ログファイルコピー中にログ出力出来ない

いまさらこんな話題と思うけど、今でも案外困る人はいて一応、メモ。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

>


ファイルの中身をのぞけば、コピー中に出力したメッセージもちゃんとファイルに記録されている。