Cygwin の eject コマンド

cygwin で abcde を動かす時に、コマンドからCD/DVD/BDドライブをejectしたい。

しかし、Cygwinではejectコマンドがない。調べてみると、Cygwin でコンパイルできるように作られたソースがあった。

Ejecting CD-ROM · GitHub

オリジナルはHow To Ejecting Removable Media in Windows NT/Windows 2000/Windows XPのようだ(現在はページが存在しないためWayback Machineで参照している。)。

コードの説明があるので、翻訳しておく:

WindowsNTバージョン4.0以降では、マシンをシャットダウンすることなく、NTFSおよびFATファイルシステムでフォーマットされたメディアのイジェクトをサポートしている。 Windows NT 3.51およびそれ以前は、シャットダウンせずにFATフォーマットされたメディアを取り出すことをサポートしている。 しかし、Windows NTバージョン3.51およびそれ以前では、システムの実行中にNTFSでフォーマットされたメディアを取り出すことはサポートされていない。 これらのバージョン3.51以前では、メディア上のデータの破損を避けるため、システムをシャットダウンする必要がある。

ボリュームがWindows NTにマウントされているとき、読み取りと書き込みの操作には2つのカテゴリーがある: 1) アプリケーションによって実行されるデータ操作と、2) Windows NTによって実行されるファイルシステム構造関連の操作である。つ目のカテゴリーは、Windows NTがファイルのディレクトリエントリ(例えば、ファイルの時間、サイズ、名前など)のようなファイルシステム自体を維持するために使用される。

Win32アプリケーションは、ファイルへのアクセスにキャッシュまたはキャッシュなしのどちらかを使うことができる。一方、Windows NTは、ファイルシステムのデータ構造への書き込み操作の一部をキャッシュし、「遅延ライタ」スキームを実装する。これにより、Windows NTはディスクへの書き込みを、絶対に必要になるまで延期することができる。こうすることで、ファイルシステムのデータが頻繁に更新される場合、最新の変更だけをディスクに書き込む必要がある。

Windows NTは、メディア上のファイルシステムデータ構造を更新するために遅延ライターシステムを使用しているため、システムがこの情報を更新している間にメディアが排出されると、メディアは破損してしまう。 この問題を回避するために、Win32アプリケーションは以下の手順で情報を更新する必要がある。

Windows NTは、メディア上のファイル・システム・データ構造を更新するために遅延ライター・システムを使用しているため、システムがこの情報を更新している間にメディアを取り出すと、メディアが破損します。この問題を回避するため、Win32アプリケーションは以下の手順でリムーバブルメディアを正しく取り出し、データ破損を防ぐ必要があります:
  1. GENERIC_READ|GENERIC_WRITE、FILE_SHARE_READ|FILE_SHARE_WRITE、およびOPEN_EXISTINGを指定してCreateFileを呼び出します。lpFileName パラメータには ˶.˶X: (X は実際のドライブ文字)を指定する。他のパラメータはすべてゼロで構わない。
  2. DeviceIoControl を介して FSCTL_LOCK_VOLUME IOCTL を発行してボリュームをロックする。他のアプリケーションまたはシステムがボリュームを使用している場合、このIOCTLは失敗する。この関数が正常に返ると、アプリケーションは、そのボリュームがシステム内の他のものによって使用されていないことを保証される。
  3. FSCTL_DISMOUNT_VOLUME IOCTLを発行して、ボリュームをマウント解除する。これにより、ファイル・システムはボリュームに関する知識をすべて削除し、ボリュームに関して保持している内部情報をすべて破棄する。
  4. IOCTL_STORAGE_MEDIA_REMOVAL IOCTLを発行して、メディアを削除できることを確認する。この IOCTL を呼び出す前に、PREVENT_MEDIA_REMOVAL 構造体の PreventMediaRemoval メンバを FALSE に設定する。これにより、デバイスがメディアの取り出しを阻止するのを止める。
  5. IOCTL_STORAGE_EJECT_MEDIA IOCTL でメディアを取り出す。デバイスが自動排出を許可していない場合は、IOCTL_STORAGE_EJECT_MEDIAをスキップして、ユーザーにメディアを取り出すように指示することができる。
  6. 最初のステップで取得したボリューム・ハンドルを閉じるか、FSCTL_UNLOCK_VOLUME IOCTL を発行する。これにより、ドライブを他のプロセスで使用できるようになる。
   #include <windows.h>
   #include <winioctl.h>
   #include <tchar.h>
   #include <stdio.h>

   // Prototypes

   BOOL EjectVolume(TCHAR cDriveLetter);

   HANDLE OpenVolume(TCHAR cDriveLetter);
   BOOL LockVolume(HANDLE hVolume);
   BOOL DismountVolume(HANDLE hVolume);
   BOOL PreventRemovalOfVolume(HANDLE hVolume, BOOL fPrevent);
   BOOL AutoEjectVolume(HANDLE hVolume);
   BOOL CloseVolume(HANDLE hVolume);

   LPTSTR szVolumeFormat = TEXT("\\\\.\\%c:");
   LPTSTR szRootFormat = TEXT("%c:\\");
   LPTSTR szErrorFormat = TEXT("Error %d: %s\n");

   void ReportError(LPTSTR szMsg)
   {
       _tprintf(szErrorFormat, GetLastError(), szMsg);
   }

   HANDLE OpenVolume(TCHAR cDriveLetter)
   {
       HANDLE hVolume;
       UINT uDriveType;
       TCHAR szVolumeName[8];
       TCHAR szRootName[5];
       DWORD dwAccessFlags;

       wsprintf(szRootName, szRootFormat, cDriveLetter);

       uDriveType = GetDriveType(szRootName);
       switch(uDriveType) {
       case DRIVE_REMOVABLE:
           dwAccessFlags = GENERIC_READ | GENERIC_WRITE;
           break;
       case DRIVE_CDROM:
           dwAccessFlags = GENERIC_READ;
           break;
       default:
           _tprintf(TEXT("Cannot eject.  Drive type is incorrect.\n"));
           return INVALID_HANDLE_VALUE;
       }

       wsprintf(szVolumeName, szVolumeFormat, cDriveLetter);

       hVolume = CreateFile(   szVolumeName,
                               dwAccessFlags,
                               FILE_SHARE_READ | FILE_SHARE_WRITE,
                               NULL,
                               OPEN_EXISTING,
                               0,
                               NULL );
       if (hVolume == INVALID_HANDLE_VALUE)
           ReportError(TEXT("CreateFile"));

       return hVolume;
   }

   BOOL CloseVolume(HANDLE hVolume)
   {
       return CloseHandle(hVolume);
   }

   #define LOCK_TIMEOUT        10000       // 10 Seconds
   #define LOCK_RETRIES        20

   BOOL LockVolume(HANDLE hVolume)
   {
       DWORD dwBytesReturned;
       DWORD dwSleepAmount;
       int nTryCount;

       dwSleepAmount = LOCK_TIMEOUT / LOCK_RETRIES;

       // Do this in a loop until a timeout period has expired
       for (nTryCount = 0; nTryCount < LOCK_RETRIES; nTryCount++) {
           if (DeviceIoControl(hVolume,
                               FSCTL_LOCK_VOLUME,
                               NULL, 0,
                               NULL, 0,
                               &dwBytesReturned,
                               NULL))
               return TRUE;

           Sleep(dwSleepAmount);
       }

       return FALSE;
   }

   BOOL DismountVolume(HANDLE hVolume)
   {
       DWORD dwBytesReturned;

       return DeviceIoControl( hVolume,
                               FSCTL_DISMOUNT_VOLUME,
                               NULL, 0,
                               NULL, 0,
                               &dwBytesReturned,
                               NULL);
   }

   BOOL PreventRemovalOfVolume(HANDLE hVolume, BOOL fPreventRemoval)
   {
       DWORD dwBytesReturned;
       PREVENT_MEDIA_REMOVAL PMRBuffer;

       PMRBuffer.PreventMediaRemoval = fPreventRemoval;

       return DeviceIoControl( hVolume,
                               IOCTL_STORAGE_MEDIA_REMOVAL,
                               &PMRBuffer, sizeof(PREVENT_MEDIA_REMOVAL),
                               NULL, 0,
                               &dwBytesReturned,
                               NULL);
   }

   AutoEjectVolume(HANDLE hVolume)
   {
       DWORD dwBytesReturned;

       return DeviceIoControl( hVolume,
                               IOCTL_STORAGE_EJECT_MEDIA,
                               NULL, 0,
                               NULL, 0,
                               &dwBytesReturned,
                               NULL);
   }

   BOOL EjectVolume(TCHAR cDriveLetter)
   {
       HANDLE hVolume;

       BOOL fRemoveSafely = FALSE;
       BOOL fAutoEject = FALSE;

       // Open the volume.
       hVolume = OpenVolume(cDriveLetter);
       if (hVolume == INVALID_HANDLE_VALUE)
           return FALSE;

       // Lock and dismount the volume.
       if (LockVolume(hVolume) && DismountVolume(hVolume)) {
           fRemoveSafely = TRUE;

           // Set prevent removal to false and eject the volume.
           if (PreventRemovalOfVolume(hVolume, FALSE) &&
               AutoEjectVolume(hVolume))
               fAutoEject = TRUE;
       }

       // Close the volume so other processes can use the drive.
       if (!CloseVolume(hVolume))
           return FALSE;

       if (fAutoEject)
           printf("Media in Drive %c has been ejected safely.\n",
                  cDriveLetter);
       else {
           if (fRemoveSafely)
               printf("Media in Drive %c can be safely removed.\n",
               cDriveLetter);
       }

       return TRUE;
   }

   void Usage()
   {
       printf("Usage: Eject <drive letter>\n\n");
       return ;
   }

   void main(int argc, char * argv[])
   {
       if (argc != 2) {
           Usage();
           return ;
       }

       if (!EjectVolume(argv[1][0]))
           printf("Failure ejecting drive %c.\n", argv[1][0]);

       return ;
   }