From 5a15091e17c25f37a6f53751ccde91f504006773 Mon Sep 17 00:00:00 2001 From: Simone Piccardi Date: Fri, 9 Nov 2018 23:52:42 +0100 Subject: [PATCH 1/1] Integrazioni su write --- fileio.tex | 94 +++++++++++++++++++++++++++++---------------------- othersock.tex | 32 ++++++++++++------ 2 files changed, 75 insertions(+), 51 deletions(-) diff --git a/fileio.tex b/fileio.tex index dc5bdfa..7b32699 100644 --- a/fileio.tex +++ b/fileio.tex @@ -537,24 +537,23 @@ univoco con l'uso delle funzioni di sez.~\ref{sec:file_temp_file}. Una volta aperto il file vi si potrà leggere o scrivere a seconda che siano utilizzati \const{O\_RDWR} o \const{O\_WRONLY}, mentre l'uso di \func{O\_RDONLY} non è consentito, non avendo molto senso ottenere un file -descriptor da cui non si potrà comunque mai leggere nulla. L'unico altro flag -che può essere utilizzato insieme a \const{O\_TMPFILE} è \const{O\_EXCL}, che -in questo caso assume però un significato diverso da quello ordinario, dato -che in questo caso non avrebbe senso fallire se il file non esiste, dato che -questo è sempre vero. - -L'uso di \const{O\_EXCL} attiene all'altro possibile impiego di -\const{O\_TMPFILE} oltre a quello della creazione sicura di un file temporaneo -come sostituto di \func{tmpfile}: la possibilità di creare un eventuale -contenuto iniziale ed impostare permessi, proprietario e attributi estesi con -\func{fchmod}, \func{fchown} e \func{fsetxattr} operando sul file descriptor, -senza possibilità di \textit{race condition} ed interferenze esterne, per poi -far apparire il tutto sul filesystem in un secondo tempo utilizzando -\func{linkat} sul file descriptor (torneremo su questo in -sez.~\ref{sec:file_openat}) per dargli un nome. Questa operazione però non -sarà possibile se si è usato \const{O\_EXCL}, che in questo caso viene ad -assumere il significato di escludere la possibilità di far esistere il file -anche in un secondo tempo. +descriptor su un file che nasce vuoto per cui non si potrà comunque leggere +nulla. L'unico altro flag che può essere utilizzato insieme a +\const{O\_TMPFILE} è \const{O\_EXCL}, che in questo caso assume però un +significato diverso da quello ordinario, dato che in questo caso il file +associato al file descriptor non esiste comunque. + +L'uso di \const{O\_EXCL} attiene infatti all'altro possibile impiego di +\const{O\_TMPFILE} oltre a quello citato della creazione sicura di un file +temporaneo come sostituto sicuro di \func{tmpfile}: la possibilità di creare +un contenuto iniziale per un file ed impostarne permessi, proprietario e +attributi estesi con \func{fchmod}, \func{fchown} e \func{fsetxattr}, senza +possibilità di \textit{race condition} ed interferenze esterne, per poi far +apparire il tutto sul filesystem in un secondo tempo utilizzando \func{linkat} +sul file descriptor (torneremo su questo in sez.~\ref{sec:file_openat}) per +dargli un nome. Questa operazione però non sarà possibile se si è usato +\const{O\_EXCL}, che in questo caso viene ad assumere il significato di +escludere la possibilità di far esistere il file anche in un secondo tempo. % NOTE: per O_TMPFILE vedi: http://kernelnewbies.org/Linux_3.11 % https://lwn.net/Articles/558598/ http://lwn.net/Articles/619146/ @@ -848,24 +847,32 @@ Si ricordi che quando un processo termina tutti i suoi file descriptor vengono automaticamente chiusi, molti programmi sfruttano questa caratteristica e non usano esplicitamente \func{close}. In genere comunque chiudere un file senza controllare lo stato di uscita di \func{close} un è errore; molti filesystem -infatti implementano la tecnica del cosiddetto \textit{write-behind}, per cui -una \func{write} può avere successo anche se i dati non sono stati -effettivamente scritti su disco. In questo caso un eventuale errore di I/O -avvenuto in un secondo tempo potrebbe sfuggire, mentre verrebbe riportato alla -chiusura esplicita del file. Per questo motivo non effettuare il controllo può -portare ad una perdita di dati inavvertita.\footnote{in Linux questo - comportamento è stato osservato con NFS e le quote su disco.} +infatti implementano la tecnica del cosiddetto \itindex{write-behind} +\textit{write-behind}, per cui una \func{write} può avere successo anche se i +dati non sono stati effettivamente scritti su disco. In questo caso un +eventuale errore di I/O avvenuto in un secondo tempo potrebbe sfuggire, mentre +verrebbe riportato alla chiusura esplicita del file. Per questo motivo non +effettuare il controllo può portare ad una perdita di dati +inavvertita.\footnote{in Linux questo comportamento è stato osservato con NFS + e le quote su disco.} In ogni caso una \func{close} andata a buon fine non garantisce che i dati siano stati effettivamente scritti su disco, perché il kernel può decidere di ottimizzare l'accesso a disco ritardandone la scrittura. L'uso della funzione -\func{sync} (vedi sez.~\ref{sec:file_sync}) effettua esplicitamente il -\emph{flush} dei dati, ma anche in questo caso resta l'incertezza dovuta al -comportamento dell'hardware, che a sua volta può introdurre ottimizzazioni -dell'accesso al disco che ritardano la scrittura dei dati. Da questo deriva -l'abitudine di alcuni sistemisti di ripetere tre volte il comando omonimo -prima di eseguire lo shutdown di una macchina. - +\func{sync} (vedi sez.~\ref{sec:file_sync}) effettua esplicitamente lo scarico +dei dati, ma anche in questo caso resta l'incertezza dovuta al comportamento +dell'hardware, che a sua volta può introdurre ottimizzazioni dell'accesso al +disco che ritardano la scrittura dei dati. Da questo deriva l'abitudine di +alcuni sistemisti di ripetere tre volte il comando omonimo prima di eseguire +lo shutdown di una macchina. + +Si tenga comunque presente che ripetere la chiusura in caso di fallimento non +è opportuno, una volta chiamata \func{close} il file descriptor viene comunque +rilasciato, indipendentemente dalla presenza di errori, e se la riesecuzione +non comporta teoricamente problemi (se non la sua inutilità) se fatta +all'interno di un processo singolo, nel caso si usino i \textit{thread} si +potrebbe chiudere un file descriptor aperto nel contempo da un altro +\textit{thread}. \subsection{La gestione della posizione nel file} \label{sec:file_lseek} @@ -1039,11 +1046,11 @@ riporti sempre la fine del file e \const{SEEK\_DATA} il valore di \param{offset}. Inoltre la decisione di come riportare (o di non riportare) la presenza di un -buco in un file è lasciata all'implementazione del -filesystem, dato che esistono vari motivi per cui una sezione di un file può -non contenere dati ed essere riportata come tale (ad esempio può essere stata -preallocata con \func{fallocate}, vedi sez.~\ref{sec:file_fadvise}) oltre a -quelle classiche appena esposte. Questo significa che l'uso di questi nuovi +buco in un file è lasciata all'implementazione del filesystem, dato che oltre +a quelle classiche appena esposte esistono vari motivi per cui una sezione di +un file può non contenere dati ed essere riportata come tale (ad esempio può +essere stata preallocata con \func{fallocate}, vedi +sez.~\ref{sec:file_fadvise}). Questo significa che l'uso di questi nuovi valori non garantisce la mappatura della effettiva allocazione dello spazio disco di un file, per il quale esiste una specifica operazione di controllo (vedi sez.~\ref{sec:file_fcntl_ioctl}). @@ -1166,8 +1173,8 @@ L'uso di \func{pread} è equivalente all'esecuzione di una \func{read} seguita da una \func{lseek} che riporti al valore precedente la posizione corrente sul file, ma permette di eseguire l'operazione atomicamente. Questo può essere importante quando la posizione sul file viene condivisa da processi diversi -(vedi sez.~\ref{sec:file_shared_access}). Il valore di -\param{offset} fa sempre riferimento all'inizio del file. +(vedi sez.~\ref{sec:file_shared_access}). Il valore di \param{offset} fa +sempre riferimento all'inizio del file. La funzione \func{pread} è disponibile anche in Linux, però diventa accessibile solo attivando il supporto delle estensioni previste dalle @@ -1198,6 +1205,10 @@ prototipo è: \begin{errlist} \item[\errcode{EAGAIN}] ci si sarebbe bloccati, ma il file era aperto in modalità \const{O\_NONBLOCK}. + \item[\errcode{EDESTADDRREQ}] si è eseguita una scrittura su un socket di + tipo \textit{datagram} (vedi sez.~\ref{sec:sock_type}) senza aver prima + connesso il corrispondente con \func{connect} (vedi + sez.~\ref{sec:UDP_sendto_recvfrom}). \item[\errcode{EFBIG}] si è cercato di scrivere oltre la dimensione massima consentita dal filesystem o il limite per le dimensioni dei file del processo o su una posizione oltre il massimo consentito. @@ -1205,13 +1216,14 @@ prototipo è: potuto scrivere qualsiasi dato. \item[\errcode{EINVAL}] \param{fd} è connesso ad un oggetto che non consente la scrittura o si è usato \const{O\_DIRECT} ed il buffer non è allineato. +% \item[\errcode{EPERM}] la scrittura è proibita da un \textit{file seal}. \item[\errcode{EPIPE}] \param{fd} è connesso ad una \textit{pipe} il cui altro capo è chiuso in lettura; in questo caso viene anche generato il segnale \signal{SIGPIPE}, se questo viene gestito (o bloccato o ignorato) la funzione ritorna questo errore. \end{errlist} - ed inoltre \errval{EBADF}, \errval{EFAULT}, \errval{EIO}, \errval{EISDIR}, - \errval{ENOSPC} nel loro significato generico.} + ed inoltre \errval{EBADF}, \errval{EDQUOT}, \errval{EFAULT}, \errval{EIO}, + \errval{EISDIR}, \errval{ENOSPC} nel loro significato generico.} \end{funcproto} diff --git a/othersock.tex b/othersock.tex index de8c3d1..bffd4ae 100644 --- a/othersock.tex +++ b/othersock.tex @@ -114,13 +114,23 @@ destinati, oppure a scartarli inviando un messaggio \textit{ICMP port \label{sec:UDP_sendto_recvfrom} Come accennato in sez.~\ref{sec:UDP_characteristics} le due funzioni -principali usate per la trasmissione di dati attraverso i socket UDP sono -\func{sendto} e \func{recvfrom}. La necessità di usare queste funzioni è -dovuta al fatto che non esistendo con UDP il concetto di connessione, non si -ha neanche a disposizione un \textsl{socket connesso} su cui sia possibile -usare direttamente \func{read} e \func{write} avendo già stabilito (grazie -alla chiamata ad \func{accept} che lo associa ad una connessione) quali sono -sorgente e destinazione dei dati. +principali usate per la trasmissione di dati attraverso i socket UDP (ed in +generale per i socket di tipo \textit{datagram}) sono \func{sendto} e +\func{recvfrom}. La necessità di usare queste funzioni è dovuta al fatto che +non esistendo con UDP il concetto di connessione, non si può stabilire (come +avviene con i socket TCP grazie alla chiamata ad \func{accept} che li associa +ad una connessione) quali sono la sorgente e la destinazione dei dati che +passano sul socket.\footnote{anche se in alcuni casi, come quello di un client + che contatta un server, è possibile connettere il socket (vedi + sez.~\ref{sec:UDP_connect}), la necessità di \func{sendto} e \func{recvfrom} + resta, dato che questo è possibile solo sul lato del client.} + +Per questo anche se in generale si possono comunque leggere i dati con +\func{read}, usando questa funzione non si sarà in grado di determinare da +quale fra i possibili corrispondenti (se ve ne sono più di uno, come avviene +sul lato del server) questi arrivino. E non sarà comunque possibile usare +\func{write} (che fallisce un errore di \errval{EDESTADDRREQ}) in quanto non +è determinato la destinazione che i dati avrebbero. Per questo motivo nel caso di UDP diventa essenziale utilizzare queste due funzioni, che sono comunque utilizzabili in generale per la trasmissione di @@ -673,10 +683,12 @@ scambiato nessun pacchetto, tutto quello che succede è che da quel momento in qualunque cosa si scriva sul socket sarà inviata a quell'indirizzo; non sarà più necessario usare l'argomento \param{to} di \func{sendto} per specificare la destinazione dei pacchetti, che potranno essere inviati e ricevuti usando -le normali funzioni \func{read} e \func{write}.\footnote{in realtà si può - anche continuare ad usare la funzione \func{sendto}, ma in tal caso +le normali funzioni \func{read} e \func{write} che a questo punto non +falliranno più con l'errore di \errval{EDESTADDRREQ}.\footnote{in realtà si + può anche continuare ad usare la funzione \func{sendto}, ma in tal caso l'argomento \param{to} deve essere inizializzato a \val{NULL}, e - \param{tolen} deve essere inizializzato a zero, pena un errore.} + \param{tolen} deve essere inizializzato a zero, pena un errore di + \errval{EISCONN}.} Una volta che il socket è connesso cambia però anche il comportamento in ricezione; prima infatti il kernel avrebbe restituito al socket qualunque -- 2.30.2