2015-09-20 10 views
5

を呼び出し、書き込み()の呼び出しは、要求されたよりも少ないバイトを書いてしまうことがあります。)は(不完全な書き込みの取り扱いのLinux/Unixでは

書き込まれたバイト数は、例えば、もしカウント未満であってもよい、 (RECIMIT_FSIZEのリソース制限を参照)、 未満のバイトを書き込んだ後に、シグナルハンドラによって コールが中断されました。 (pipe(7)も参照)

C標準ライブラリのfwrite()も同じ動作をします。私が見たほとんどのコードは次のようにエラーを処理するために選択し、この可能性を無視します:

int ret = write(fd, buf, size); 
if (ret < 0) { 
    printf("Couldn't write %s: %s\n", path, strerror(errno)); 
    exit(1); 
} 

我々は

if (ret != size) { 
    printf("Couldn't write %s: %s\n", path, strerror(errno)); 
    exit(1); 
} 

をチェックしているように、私は個人的に条件を変更するのが習慣に得ていますこれはこの状態に気付く。

Couldn't write /some/file: Success 

私は、これはあまりにも驚くべきことではないと仮定します。しかし、私はまた私のプログラムは時々で終了することに気付きました。しかし、このケースを扱うための標準的で堅牢でクリーンな方法は何ですか?明らかに "サイレントデータの破損" ---これまでのすべてのチュートリアルの動作であるように思われます---うまくいきません。私はこのケースを特別に検出して終了するように自分のコードを修正することができました。

しかし、man 2 writeで提供される例は単なる例です。もう一度やり直す方法がありますか(EINTRは例です...)?これらを検出するにはどうすればよいですか、もっと重要なのは、すべてのケースを処理したことを確認することですか?これらのエラーハンドラを作成するための標準的なクリーンな方法はありませんか?何も2つの状況下で書き込まれていない場合

+1

書き込みは* write * someまたはすべてのデータで成功することができます。この場合、 'errno'を設定せずに書き込まれたデータの量を返します。常に '-1'を返し、' errno'をセットします。例えば、 'write()'が*いくつかの*データを書き込んだ後にシグナルによって中断された場合、 'errno'を' EINTR'に設定しません。これは 'write()'が中断されて、まったく。その結果、 'write()'は、 'size'の値が正の値で呼び出されたとき、決して' 0'を返しません。 – fuz

+0

私はそれを得るために始めている参照してください。これを利用して、私はwrite()が*いくつかの*データの書き込みに成功した後、直ちに操作を繰り返すと、*それ以上のデータを書き込むことができず、 "デバイス上にスペースが残っていません。私は、無限の "データを書くことはできませんが、エラーもありません"というループに陥ることを恐れていました。 –

+0

データが書き込まれていない場合、 'write()'は 'size'に0を渡さない限りエラーを返します。エラー状態がなければ0を返すことができます。 'write()'は、私が知る限り0以外の長さの引数に対して0を返すことはできません。 – fuz

答えて

5

書き込みは負の数を返します。

  • 一時的なエラー(例えばEINTREAGAIN、及びEWOULDBLOCK)。これらのうちの最初のものは書き込みで発生する可能性があり、2番目のものは非ブロックI/Oでのみ発生します。

  • 恒久的なエラー。

通常は、ルーチンはEINTREAGAINまたはEWOULDBLOCKが返された場合(私は後者に対して引数を見てきましたが)、書き込みを繰り返しているので、最初の再試行したいと思います。例えば

ssize_t 
write_with_retry (int fd, const void* buf, size_t size) 
{ 
    ssize_t ret; 
    do 
    { 
     ret = write(fd, buf, size); 
    } while ((ret<0) && (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK)); 
    return ret; 
} 

はまた、その書き込みは、あなたが、ノンブロッキングI/Oの場合には要求されたよりも少ないが書き込まれたバイト数を返す、またはIを遮断することができます(manページから)注意/ O(Linuxのmanページが明確になるので)。

OS-Xマンページの抽出:

そのようなフロー制御の対象であるソケット、などのオブジェクト、write()writev()に非ブロッキングI/Oを使用すると、要求されたよりも少ないバイトを書き込むことができます。戻り値に注意を払う必要があり、残りの操作は可能な場合には再試行する必要があります。

Linuxのマン・ページエキス(私の強調):

例えば、不十分な基礎となる物理媒体上のスペース、またはRLIMIT_FSIZEがある、あれば書き込まれたバイト数を数えるよりも小さくすることができます(setrlimit(2)を参照)、がコールされたか、カウントバイト未満のバイトが書き込まれた後にシグナルハンドラによってコールが中断されました。

あなたは、通常select()とのそれらを処理されますが、手動でそのケースを扱うために:

ssize_t 
write_with_retry (int fd, const void* buf, size_t size) 
{ 
    ssize_t ret; 
    while (size > 0) { 
     do 
     { 
      ret = write(fd, buf, size); 
     } while ((ret < 0) && (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK)); 
     if (ret < 0) 
      return ret; 
     size -= ret; 
     buf += ret; 
    } 
    return 0; 
} 
+0

最後にメモが私の場合に関連するものです。私はソケットや割り込み、非ブロックモードを使用していません。しかし、提供される情報はすべて有用で興味深く、よく書かれています。ありがとうございました! –

+0

シグナルの使用を避けるのは難しいかもしれないことに注意してください(EINTRの原因)。たとえば誰かが '^ Z'を押すかもしれません。 – abligh

+0

あなたは '^ C'を意味しますか?私は '^ C'のカスタムシグナルハンドラを設定しないので、部分的な書き込みは予想される動作です。 あなたが本当に '^ Z'を意味するなら、それは悪くなるでしょう。私は '^ Z'が' SIGTSTP'を送ったと思った。私たちは本当にそれからEINTRを得ることができますか? –

2

(ret < 0)条件が短い書き込みを無視し、唯一の正式なエラーを報告します。これは間違いのように間違っていますが、それを修正するには余分なコードが必要です。writeシステムコールをラップした関数を使用する必要があると考えるのは妥当です。

(ret != size)条件ははるかに敏感テストなどですが、フォローアップ活動が再び、それはwrite()巻き付け関数であることが必要であることを十分に複雑です。汎用のラッパー関数で

、あなたは区別する必要があります。

  • ret > 0 && ret < size:この場合、あなたはバッファの残留物を書くためにループしてみてください。実際に書き込まれたバイト数を単純に返すことができます(累積的に)。

  • ret == 0:あなたはゼロバイトを書きましたが、エラーはありませんでした。 ファイルディスクリプタをO_NONBLOCKで開いている場合に発生する可能性があります。おそらくこれに対処するための戦略があるはずですが、コンテキストに依存することになります。適切な睡眠ですか?それとも、やり直してシステムを叩く危険にさらされますか?あなたは何度か試してみませんか? 0バイトの書き込みを要求した場合、0バイトが書き込まれますが、それはret == sizeの対象となります。それ以外の場合は、ret == 0が可能であるかどうかは不明です。一般的なラッパーの場合、最良の方法は、0を返すことです。

  • ret < 0:再試行が失敗する可能性が高いです。エラー状態を返します。しかし、選択されたいくつかのエラーの1つを取得すると、再試行する価値があります。write()がシグナルによって中断された場合はEINTR、ブロックする非ブロックファイルディスクリプタ(EWOULDBLOCKとも呼ばれることもあります)に書き込むためのEAGAIN。おそらくいくつか他のものがありますが、write()の通常のエラーのどれもそのものではありません。

ほとんどの深刻なUnix(またはLinux)システムプログラミングの書籍でこれについて議論しています。例:特定のFUZxxlさんのコメントが指摘して掲載

0

答え、非常に便利です重要な事実:

write()はデータを書き込まなければ決して成功しません。私自身のケースでは

、私は、ディスクの空き容量が不足している、と私は本当には私にENOSPCを与えるためにwrite()を望んでいた。解決策は、不完全なwrite()コールを再試行することです。最初の呼び出しは正常に戻ります。 2番目の呼び出しはデータを書き込めません。 何も書かないので、を入力した後、ENOSPCを返します。無限にループしてデータをフルディスクに書き込もうとしません。 write()がすべてのデータの書き込みに合理的に失敗する可能性があるその他の理由にも対応できます。

関連する問題