ログファイルコピー中にログ出力出来ない
いまさらこんな話題と思うけど、今でも案外困る人はいて一応、メモ。Windows限定だけど。
問題は、数百MByteあるログファイルを、バックアップ目的でコピーをとりたいが、ログファイルコピー中にアプリケーションがログ出力のためにファイルを開くことが出来ずエラーを起こしてしまう事があるということ。
具体的にはこんな状況(a.log のファイルサイズは数100MByte)。
>start copy /Y a.log a.bak>echo a message>>a.log
プロセスはファイルにアクセスできません。別のプロセスが使用中です。>
これは、どうやらWindows標準のcopyコマンドがソースになるファイルのオープン時に FILE_SHARE_WRITE を指定していないから(書き込みに対して排他的にファイルをオープンするから)。
・・まあ、この手のエラーは大抵の場合仕方ないであきらめるのが通例だけど。
それでもアプリケーション稼動時にログファイルのバックアップコピーを作って、且つアプリケーションのエラー出力を邪魔したくないというときは、以下のようなプログラムを作って使う必要がある。つまりファイルオープン時にFILE_SHARE_WRITEを指定するコピーコマンド。ここではgraceful-copyと名づけた。
graceful-copy.c
#include <stdio.h>
#include <windows.h>HANDLE fs;
HANDLE fd;
char buff[4096];void init() {
fs = INVALID_HANDLE_VALUE;
fd = INVALID_HANDLE_VALUE;
}
void myexit(UINT rc) {
if (fs != INVALID_HANDLE_VALUE) CloseHandle(fs);
if (fd != INVALID_HANDLE_VALUE) CloseHandle(fd);
ExitProcess(rc);
}
int main( int argc, char *argv[] )
{
DWORD rb, wb;
if (argc < 3) {
printf("usage: %s <srcfile> <dstfile>\n", argv[0]);
return 10;
}
fs = CreateFile(argv[1], GENERIC_READ, FILE_SHARE_READ|FILE_SHARE_WRITE,
NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL|FILE_FLAG_SEQUENTIAL_SCAN,
NULL);
if (fs == INVALID_HANDLE_VALUE) {
fprintf(stderr, "can't open srcfile.\n");
myexit(10);
}
fd = CreateFile(argv[2], GENERIC_WRITE, FILE_SHARE_READ, NULL,
CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL|FILE_FLAG_SEQUENTIAL_SCAN,
NULL);
if (fd == INVALID_HANDLE_VALUE) {
fprintf(stderr, "can't open dstfile.\n");
myexit(20);
}
while (1) {
if (ReadFile(fs, buff, sizeof(buff), &rb, NULL) != 0) {
if (rb == 0) break;
} else {
fprintf(stderr, "file read error.\n");
myexit(30);
}
if (WriteFile(fd, buff, rb, &wb, NULL) == 0) {
fprintf(stderr, "file write error.\n");
myexit(40);
}
}
myexit(0);
return 0;
}
こいつを、たとえば Visual C++(セットアップとかはこちら参考)を使ってビルドして、さっきと同じようなことを上記のプログラムを使って実行すると、今度はエラーにならない。
>start graceful-copy a.log a.bak>echo a message>>a.log
>
ファイルの中身をのぞけば、コピー中に出力したメッセージもちゃんとファイルに記録されている。