リンクとコピー (DBMError: dbm_store failed in ruby1.8.7_p160 その4)


長らく私を悩ませているRuby-DBMがまともに動かない件だが、件のgdbm.dllをデバッグすることでようやく原因が判明したかもしれない。

そもそもgdbmはdbmの実装を包含しており、殆どのdbm_〜関数はgdbm_〜関数を内部で呼び出すことになるが、レガシDBMの仕様を実現するためにdbm独自の実装が追加されているようだ。

  • dbmopen.c (Line:83〜99)
  gdbm_file_info *temp_dbf;  /* Temporary file pointer storage. */
  :
  flags &= O_RDONLY | O_RDWR | O_CREAT | O_TRUNC;
  if (flags == O_RDONLY)
  {
      temp_dbf = gdbm_open (pag_file, 0, GDBM_READER, 0, NULL);
  }
  else if (flags == (O_RDWR | O_CREAT))
  {
      temp_dbf = gdbm_open (pag_file, 0, GDBM_WRCREAT, mode, NULL);
  }
  else if ( (flags & O_TRUNC) == O_TRUNC)
  {
      temp_dbf = gdbm_open (pag_file, 0, GDBM_NEWDB, mode, NULL);
  }
  else
  {
      temp_dbf = gdbm_open (pag_file, 0, GDBM_WRITER, 0, NULL);
  }

上記dbm_openもその例に漏れず、内部ではGDBMの定数を使ってgdbm_open関数を呼び出し、データベース用のファイルを開いてdbf(データベースファイルのディスクリプタと思われる)構造体を返す。

DBMはそのファイルも古のUNIX DBM形式であり、データベース用のファイルは拡張子".db"ではなく、".pag"と".dir"の二つのファイルで構成される。

//test_dbm.rbで作られるテスト用のDB
tmptest_dbm_.dir
tmptest_dbm_.pag

ダンプして見ると実際にはこの二つのファイルの中身は同じだが、dbmopen.cの実装を調べてみてみるとdbm実装では作成したデータベース(ファイル)が新規作成だった場合にだけ、DBM形式のファイルを生成するのにファイルの複製をリンクで生成していることが分かる。

  • dbmopen.c (Line:125〜132)
  /* Since we can't stat it, we assume it is not there and try
     to link the dir_file to the pag_file. */
  if (link (pag_file, dir_file) != 0)
  {
      gdbm_errno = GDBM_FILE_OPEN_ERROR;
      gdbm_close (temp_dbf);
      temp_dbf = NULL;
      goto done;
  }
}

このように、"データベース名.pag"から"データベース名.dir"のリンクを生成するようにlink関数が呼ばれているが、以前にエントリで書いた「一度目のデータベースオープン(作成)に失敗」する、という特徴的な事象は、このif文が真に、つまりテスト開始時にデータベースファイルを生成する際にエラーリターンしていることが判明したのである。

さて、いよいよ核心に近づいてきた訳だが、ここで呼ばれているlink関数は、Windows(Win32)の場合UNIXシステムコールではなく(当たり前だ)、同梱されているwin32.c(win32/win32.c)にわざわざ実装があるので、見てみよう。

  • win32.c (Line:1〜18)
/*
 * fsync and link emulation for windows
 *
 * this part is public domain.
 */
#include 
#include 
#include 

int fsync(int fd)
{
    return !FlushFileBuffers( (HANDLE)_get_osfhandle(fd));
}

int link(const char *src, const char *dst)
{
    return !CopyFile(src, dst, TRUE);
}

CopyFileはVC++ 2005のSDKに付属しているるWinBase.h(%VSINSTALLDIR%\VC\PlatformSDK\Include\WinBase.h)を参照していると思われる。

  • WinBase.h (Line:7513〜7522)
#ifdef UNICODE
    return CopyFileW(
#else
    return CopyFileA(
#endif
        lpExistingFileName,
        lpNewFileName,
        bFailIfExists
        );
}

これはUNICODEサポートに合わせて適切なCopyFile APIに置き換えているだけだ。

つまり、gdbm(for Windows)の実装の場合".pag"ファイルから".dir"ファイルへのリンクの生成は実際にはWin32 APIのCopyFileA又はCopyFileWが呼ばれているということになる。

ハードリンクではなくコピー?

これって正しいのかな?
昔ならいざ知らず、今であればCreatehardlinkとか使うんじゃないだろうかと思うのだが。

答えはほぼ出ているのだが、続きは次のエントリで。