Stai erroneamente combinando funzioni IO bufferizzate e non bufferizzate. Tale combinazione deve essere eseguita con molta attenzione soprattutto quando il codice deve essere portabile. (ed è brutto scrivere codice non portabile...)
È sicuramente meglio evitare di combinare IO con buffer e senza buffer sullo stesso descrittore di file.
IO bufferizzato: fprintf()
, fopen()
, fclose()
, freopen()
...
IO senza buffer: write()
, open()
, close()
, dup()
...
Quando usi dup2()
per reindirizzare stdout. La funzione non è a conoscenza del buffer che è stato riempito da fprintf()
. Quindi, quando dup2()
chiude il vecchio descrittore 1 non svuota il buffer e il contenuto potrebbe essere scaricato su un output diverso. Nel tuo caso 2a è stato inviato a /dev/null
.
La soluzione
Nel tuo caso è meglio usare freopen()
invece di dup2()
. Questo risolve tutti i tuoi problemi:
- Svuota i buffer del
FILE
originale flusso. (caso 2a) - Imposta la modalità di buffering in base al file appena aperto. (caso 3)
Ecco la corretta implementazione della tua funzione:
void RedirectStdout2File(const char* log_path) {
if(freopen(log_path, "a+", stdout) == NULL) err(EXIT_FAILURE, NULL);
}
Sfortunatamente con IO bufferizzato non puoi impostare direttamente le autorizzazioni di un file appena creato. Devi usare altre chiamate per cambiare i permessi o puoi usare estensioni glibc non portatili. Vedi il fopen() man page
.
Flushing per stdout
è determinato dal suo comportamento di buffering. Il buffering può essere impostato su tre modalità:_IOFBF
(buffering completo:attende fino a fflush()
se possibile), _IOLBF
(line buffering:la nuova riga attiva lo svuotamento automatico) e _IONBF
(scrittura diretta sempre usata). "Il supporto per queste caratteristiche è definito dall'implementazione e può essere influenzato dal setbuf()
e setvbuf()
funzioni." [C99:7.19.3.3]
"All'avvio del programma, tre flussi di testo sono predefiniti e non devono essere aperti esplicitamente:input standard (per leggere l'input convenzionale), output standard (per scrivere l'output convenzionale) e errore standard (per scrivere l'output diagnostico). Come inizialmente aperto, l'errore standard il flusso non è completamente bufferizzato; i flussi standard input e standardoutput sono completamente bufferizzati se e solo se è possibile determinare che il flusso non fa riferimento a un dispositivo interattivo." [C99:7.19.3.7]
Spiegazione del comportamento osservato
Quindi, quello che succede è che l'implementazione fa qualcosa di specifico della piattaforma per decidere se stdout
verrà bufferizzato in linea. Nella maggior parte delle implementazioni di libc, questo test viene eseguito quando lo stream viene utilizzato per la prima volta.
- Il comportamento n. 1 è facilmente spiegabile:quando lo stream è per un dispositivo interattivo, è bufferizzato in linea e il
printf()
viene scaricato automaticamente. - Ora è previsto anche il caso n. 2:quando reindirizziamo a un file, lo stream è completamente memorizzato nel buffer e non verrà scaricato se non con
fflush()
, a meno che non ci scriva un sacco di dati. - Infine, comprendiamo anche il caso n. 3 per le implementazioni che eseguono il controllo sull'fd sottostante solo una volta. Perché abbiamo forzato l'inizializzazione del buffer di stdout nel primo
printf()
, stdout ha acquisito la modalità con buffer di riga. Quando sostituiamo fd per passare al file, è ancora bufferizzato, quindi i dati vengono scaricati automaticamente.
Alcune implementazioni effettive
Ogni libc ha libertà nel modo in cui interpreta questi requisiti, dal momento che C99 non specifica cosa sia un "dispositivo interattivo", né la voce stdio di POSIX lo estende (oltre a richiedere che stderr sia aperto per la lettura).
-
Glibc. Vedere filedoalloc.c:L111. Qui usiamo
stat()
per verificare se fd è un tty e impostare la modalità di buffering di conseguenza. (Questo è chiamato da fileops.c.)stdout
inizialmente ha un buffer nullo, ed è allocato al primo utilizzo del flusso in base alle caratteristiche di fd 1. -
Libc BSD. Codice molto simile, ma molto più pulito da seguire! Vedi questa riga in makebuf.c