Android NDKのfseek,lseek


ファイルseekについては、stream上で動作するfseek、fseeko、file descriptor上で動作するlseek、lseek 64など、一連の関数があります.以下に、いくつかの関数のプロトタイプを示します.
int fseek(FILE *stream, long offset, int whence);
int fseeko(FILE *stream, off_t offset, int whence);
off_t lseek(int fd, off_t offset, int whence);
off64_t lseek64(int fd, off64_t offset, int whence);

 
2 G以上の大きなファイルの場合、offsetが2 Gより大きい場合は、offsetが64ビットのタイプでなければなりません.
上のoffsetのタイプから見ると、long,off_の3種類がありますt,off64_t
off_tとoff 64_tはtypedefから出てきた新しいタイプで、明らかにoff 64_tは64ビットに違いない.つまりlseek 64は大きなファイルをサポートしているに違いない.
longとoffの場合t,fseekoのmanマニュアルには次の言葉があります.
man fseekoは
On many architectures both off_t and long are 32-bit types, but compilation with
#define _FILE_OFFSET_BITS 64
will turn off_t into a 64-bit type.
off_の場合tは、マクロコンパイルパラメータを1つ加えるだけで64ビットにすることができる.
longの場合、その長さはシステムおよびコンパイラに依存し、32ビットプラットフォーム、longは32ビット、64ビットプラットフォーム、longは32ビット、または64ビットであり、これはコンパイラに依存する.
まとめると、fseekは32ビットプラットフォームでは大きなファイルをサポートできず、64ビットプラットフォームでは大きなファイルをサポートする可能性があります(コンパイラによって異なります).fseekoとlseekはマクロパラメータ設定により、大きなファイルをサポートすることができる.lseek 64は、大きなファイルをサポートする関数名から見ることができます.
 
上はLinux向けですが、今はAndroidです.
Androidでは、fseekは大きなファイルをサポートできません.fseekoとlseekは、マクロを設定しています.FILE_OFFSET_BITSの後は、やはりダメですが、Googleの後にAndroidがサポートしていないことに気づきました.https://code.google.com/p/android/issues/detail?id=64613
このページは開けにくいので、ここに内容を貼ってください.
Issue 64613:implement _FILE_OFFSET_BITSは
Reported by [email protected], Jan 8, 2014
This is arguably a dupe of Issue #55866 (NDK: Missing large file support), but that bug is still in NotEnoughInformation, so lets provide more information...
The NDK currently declares e.g.
extern off_t lseek(int, off_t, int);
extern off64_t lseek64(int, off64_t, int);
While this provides "large file support", it does not go as far as glibc does. On "proper"Linux, it's more complicated; if _FILE_OFFSET_BITS is set to 64, then the "normal"file I/O functions are 64-bit -- lseek(2) would take a 64-bit off_t, not a 32-bit off_t.
http://users.suse.com/~aj/linux_lfs.html
> In a nutshell for using LFS you can choose either of the following:
> * Compile your programs with "gcc -D_FILE_OFFSET_BITS=64". This forces all
> file access calls to use the 64 bit variants.
(See also e.g. glibc and which has lots of fun/complicated #if-fu.)
Many open-source libraries will use autoconf's AC_SYS_LARGEFILE macro (or variants thereof) in order to check for and enable 64-bit off_t on 32-bit platforms, effectively resulting in:
#define lseek lseek64
http://www.gnu.org/software/autoconf/manual/autoconf-2.69/html_node/System-Services.html
The problem with the NDK is that it doesn't support any of these patterns. Consequently, software built to support _FILE_OFFSET_BITS=64 behavior won't be built with this support enabled (because the NDK doesn't support it), resulting in use of the 32-bit file APIs instead of the 64-bit APIs.
https://code.google.com/p/android/issues/detail?id=55866#c6
> I have the same problem with my ffmpeg build. Can't open video files larger than 2GB.
What would be useful is for the NDK to implement/conform to the current glibc macros/patterns so that software with large file support can easily make use of it on Android.
Jan 9, 2014 Project Member #1 [email protected]
we don't actually have the full set of *64 functions yet either, but we're working on it.
Summary: implement _FILE_OFFSET_BITS (was: NDK: Missing "GNU compatible"large file support.)
Owner: [email protected]
Cc: [email protected] [email protected]
Nov 6, 2014 #2 [email protected]
This issue hit me recently in migrating some code that was safe using autoconf (AC_SYS_LARGEFILE) and I tried (paranoid) adding in `#define _FILE_OFFSET_BITS 64` all over the place.
Finally realized with a very small test program that Android does not respect `#define _FILE_OFFSET_BITS 64` or the autoconf macro as expected.
This led to a maddening bug that was hard to track down as core expectations were not correct.
Is this non-conformance documented anywhere?
 
はい、lseek 64しかありません.Androidでこれをサポートしています.
しかしどのように使いますか、前のコードはすべてfopen、fread、fseek、ftellシリーズの関数を使って、幸いfilenoという関数があります.
int fileno(FILE *stream);

この関数はstreamをfile descriptorに変換します.
 
次に、自分の64ビットをサポートするfseek関数をカプセル化し、fseekとlseek 64の戻り値に注意する.
int fseek_64(FILE *stream, int64_t offset, int origin) {
    int fd = fileno(stream);
    if (lseek64(fd, offset, origin) == -1) return errno;
    return 0;
}

こうして、it works.
しかし、プログラムがしばらく走った後、少し異常に気づいて、追跡して、bugは私たちが書いたfseekにロックされました.64をクリックします.
具体的にはfreadでいくつかのデータを読み、fseek_64、それからfread、読んだデータが私たちが望んでいないことを発見して、私たちが望んでいるデータの前に、いつもいくつかの汚いデータがあって、そこでfreadがキャッシュしているのではないかと推測して、汚いデータはキャッシュの中で読み終わっていないデータですか?なぜfseekは問題なくlseek 64のfseekを呼び出したのか64に問題がありますか?fseekとlseekの間にはどんなつながりがありますか.またどんな違いがありますか.そこでgoogleは、私の推測を証明しました.
まずfread/fwriteシリーズ関数は実装時に確かにキャッシュを用いた.lseekはシステム呼び出しであり、fseekは標準cライブラリであり、その下位実装もlseekを呼び出したが、同時にキャッシュに対応する処理を行った.例えば、キャッシュに10バイトのデータがあると仮定すると、このとき4バイト後ろにジャンプします.これはfseekがlseekを呼び出す必要はありません.キャッシュのポインタを4バイト後ろにずらせばokです.40バイト後ろにジャンプする場合は、fseekがlseekを呼び出し、指定した場所にジャンプし、キャッシュを空にします.
我々のfseek_64インプリメンテーションでは、lseek 64を呼び出して指定された場所にジャンプするだけで、キャッシュを操作しないため、上記のバグが発生します.
ここではsefbuf関数を使用してキャッシュを操作します.そこでfseek_を修正64の関数は次のとおりです.
int fseek_64(FILE *stream, int64_t offset, int origin) {
    setbuf(stream, NULL); //  buffer
    int fd = fileno(stream);
    if (lseek64(fd, offset, origin) == -1) return errno;
    return 0;
}

このように変えた後、上のバグはなくなりました.
 
最後に、プロジェクトをする過程で、EOFに到着したstreamに対してlseekを使用するとstreamを再び読むことができないという問題があります.fseek関数がこれを処理しているかどうか分かりませんが、処理があれば(今のところこのような可能性が高いと感じています)、私たちのfseek_64関数は、rewindを使用してファイルを再読み取りできるように改善し続ける必要があります.もしそうであれば、コードはこのように変更する必要があります.
int fseek_64(FILE *stream, int64_t offset, int origin) {
    if (feof(stream)) {
        rewind(stream);
    }
    else {
        setbuf(stream, NULL); //  fread   
    }
    int fd = fileno(stream);
    if (lseek64(fd, offset, origin) == -1) {
        return errno;
    }
    return 0;
}

後で検証してから検証結果を更新します.
 
update:
検証済み、fseekはEOF済みstreamを再読み取り可能にし、rewindも可能です.だから、上のコードは動作します.