プログラマから見た NTFS 2000 Part1: ストリームとハード リンク

Dino Esposito

March 2000

要約 : この記事では、Microsoft(R) Windows(R) 2000 の新しいファイル システムである NTFS 2000 について詳しく説明します。

内容

はじめに
NTFS 2000 の概要
複数のファイル ストリーム
ストリームの基本
ストリームのバックアップと列挙
ハード リンク
NTFS の機能を享受する
まとめ

はじめに

Microsoft Windows NT(R) の完全なオブジェクト指向バージョンが出るという噂は、1994 年以降しばらくの間ささやかれていました。Cairo というコード ネームを持つ NT のこの伝説的なバージョンが、米国レッドモンドにある研究所の外で具体化されることはありませんでした。しかし Cairo の基本的なアイデアの一部は、Cairo のスタート以後これまでに時折導入されてきました。

Cairo の背景にある基本的な考え方は、ファイルとフォルダをオブジェクトやオブジェクトの集合とするというものでした。フォルダの内容は、基礎となるファイル システムのストレージ メカニズムに必ずしも拘束されず、ユーザーはこれらのオブジェクトを独立した単独のエンティティとしてアクセスおよび複製することができます。ファイルとフォルダのオブジェクトは、標準のメソッドとプロパティ、および所有者や作成者が定義したメソッドとプロパティによって、プログラム可能な API を公開するという構想でした。

現在、その代わりに使用されているのは、内部構造にファイルとフォルダを登録するファイル システムで、ファイルやフォルダをディスク上で移動すると、この構造が複製されます。ファイルとフォルダには一連の機能がありますが、あまりに小規模で、最新のアプリケーションのニーズには対応できません。部分的な取り組みとして、ファイルとフォルダに追加情報を付加するための技術がこれまでにいくつか提供されてきました。シェルと名前空間の拡張、desktop.ini ファイル、FileSystemObject、およびシェル オートメーション オブジェクト モデルはそのほんの一例です。しかし、これらの機能は、スポット的で部分的な解決策に過ぎません。これらの機能には、Windows ファイル システムの根本的な改革というポイントがまったく欠けています。下位互換性が重大な問題であることから、Windows ではいまだにファイル アロケーション テーブル (FAT) の上に構築された旧式のファイル システムが利用されています。FAT の始まりは、実に Microsoft MS-DOS(R) バージョン 2.0 にまでさかのぼります。最近になって、大容量ハード ディスクのサポートを始めとするいくつかの改善が見られましたが、それでも FAT はファイルとフォルダの情報を格納する方法として十分なものではありません。

長年にわたる実際の経験から、最も重大な制限は、プログラマがファイルの適切な管理と識別に必要とする追加情報に関係することがわかりました。先日、筆者は、Word 97 文書の "本当の" 作成日を取得するように依頼されました。これは簡単な作業だと思われるかもしれません。というのは、作成日が属性であり、いくつかの API 関数を使って簡単に取得できるからです。ただし、それは一部のみ事実です。同じ Word ファイルを別のマシン、あるいは同じフォルダにコピーして、両方のコピーの作成日を比較してみてください。驚くことに作成日が異なっています。コピーを作成すると、まったく新しいファイルが作成され、その作成日のタイム スタンプが使用されるのです。コピーで作業を行うと、ファイルが最初に作成された日に関する貴重な情報が失われてしまいます。

幸い Word 文書は、この情報を内部の SummaryInformation フィールドに保持しています。したがって、筆者の場合は問題を解決して顧客に料金を請求することができました。しかし、これが Access ファイルやテキスト ファイルだったら、筆者の努力も無駄に終わってしまうでしょう。

Microsoft は、Windows NT で NTFS という新しいファイル システムを導入しました。NTFS で最も目を引く機能は、大きなフォルダでのファイル検索を高速化する B ツリー構造、ファイルベースのセキュリティ、ログ収集、拡張されたファイル システムの回復性、および FAT や FAT32 よりもはるかに優れたディスク領域の使い方です (ちなみに、Windows 2000 では FAT32 ボリュームに対する完全なサポートとアクセスを提供しています)。

NTFS ボリュームには、Windows 3.1 で登場したときから備わっているもう 1 つの過小評価されがちな機能があります。それは複数のデータ ストリームを 1 つのファイルに収める機能です。Windows 2000 ではストリームのサポートが強化されたほか、ファイルをシームレスに処理するための便利な機能が追加されました。では、Windows 2000 付属の NTFS である NTFS 2000 の主要な機能について見ていきましょう。

NTFS 2000 の概要

複数データ ストリームが NTFS 2000 ボリューム ファイルの唯一の機能というわけではなく、ほかにも Windows 2000 の動作に必要な機能がいくつかあります。

  • ファイルとディレクトリの暗号化

  • ユーザーごと、ボリュームごとのディスク クォータ

  • 再解析ポイントと階層型ストレージ管理

  • マウント ポイント

  • ハード リンク

  • 変更ジャーナル

Windows 2000 をインストールする際、Windows 2000 のボリュームを NTFS 2000 に変換するかどうかをたずねられます。ただし、NTFS 2000 ファイル システムを、どうしても使用する必要があるのは、ドメイン コントローラとして機能するマシンだけです。FAT パーティションから NTFS への変換は、コマンドライン ユーティリティの convert.exe を使っていつでも実行できます。

   CONVERT volume /FS:NTFS [/V]

ボリューム引数には、ドライブ文字にコロンを付加したものを指定します。ここにはマウント ポイント、つまりボリューム名を指定することもできます。/FS:NTFS オプションは、ボリュームを NTFS に変換するように指定します。ユーティリティを詳細モードで実行する場合は、最後に /V を指定します。convert.exe を実行すると、一定の初期化が行われ、その後で再起動するかどうかをたずねられます。実際の変換は、次回の起動の直後に行われます。

上記の機能以外に、Windows 2000 の全体的なフォルダ管理には注目すべき側面があります。それは、desktop.ini ファイルに対するサポートが、完全かつ多少拡張されている点です。この記事の残りの部分では、主にストリームとハード リンクについて説明しますが、次の表 1 ではそれ以外の NTFS 2000 の主要機能の最重要ポイントをまとめておきます。

表 1 NTFS 2000 の主要機能

機能 説明
暗号化されたファイル システム 管理者は、NTFS 2000 のファイルとフォルダを選択して暗号化することができます。この暗号化は、ユーザーからは見えません。コアの Windows API は、ファイルの暗号化属性を認識しています。この属性を維持するには、ファイルを移動またはコピーする場合に、独自の関数を使用しないで、システムの CopyFile() または MoveFile() を使用します。
ディスク クォータ ディスク クォータによって、ユーザーごとに最大ディスク領域を設定することができます。また、ユーザーがある警告レベルを超えた場合に通知を受けることも可能です。クォータの機能自体はユーザーからは見えません。ユーザーに見えているのはディスク領域の空き容量だけです。
再解析ポイント 再解析ポイントは、ファイルやフォルダに割り当てるユーザー定義データの集合です。このデータの形式を認識するのは、データを格納するアプリケーション、およびファイルやフォルダに対してそのデータを解釈し処理するためにインストールされるファイル システム フィルタです。
スパース ファイル スパース ファイルとは、大量のデータを含まない巨大なファイルです。スパース ファイル機能を使用すると、ファイル内のゼロ以外のデータが書き込まれた部分のみにハード ドライブの領域が割り当てられます。
マウント ポイント ボリューム マウント ポイントは、別のボリュームを "マウントする" 場所となる既存のパスです。これを設定すると、ユーザーとアプリケーションは、マウントされたボリュームをそのパスで参照することができます。この機能によって、種類の異なるファイル システムを 1 つの論理ファイル システムに統合することができます。
変更ジャーナル 変更ジャーナルは、NTFS 2000 ボリューム上で変更されたすべてのファイルやディレクトリの一覧を含んだデータベースです。ジャーナルはボリュームごとに作成され、ファイルやフォルダのすべての変更を表すレコードを格納します。
Desktop.ini desktop.ini ファイルには、フォルダのカスタマイズに関するすべてのデータが保存されています。このファイルは、フォルダのアイコンと infotip に関する情報を格納する小さなテキスト ファイルです。Windows 2000 では、このようなファイルが UI レベルで完全にサポートされています。

複数のファイル ストリーム

NTFS ファイル システムでは、各ファイルを複数のデータ ストリームで構成することができます。指摘しておきたいのは、ストリームが NTFS 2000 の新機能ではなく、Windows NT 3.1 の頃からあったという点です。非 NTFS ボリューム (Windows 98 マシンのディスク パーティションなど) でファイルの内容を読み取る場合は、1 つのデータ ストリームにしかアクセスできません。したがって、そのストリームがファイルの実際の "一意な" 内容として認識されます。このようなメイン ストリームには名前がなく、NTFS 以外のファイル システムは、このストリームしか処理できません。一方、NTFS ボリュームでファイルを作成する場合は状況が変わります。図 1 はその概要を示しています。

ms810604.ntfs01(ja-jp,MSDN.10).gif

図 1 複数ストリーム ファイルの構造

複数ストリーム ファイルは、単一ストリーム ファイルを集めて同じファイル システム エントリに組み込んだようなものです。このファイルは、一見するとユニークで原子的な単位のようですが、実は多数の独立したサブユニットから構成されていて、それらを個別に作成、削除、および変更することができます。一般的なプログラミングのシナリオでは、ストリームを使うと非常に便利な場合が多々あります。ただしストリームを使用する場合に注意が必要なのは、NTFS に対応していないストレージ装置 (CD、フロッピー、非 NTFS ディスク パーティションなど) に複数ストリーム ファイルをコピーすると、追加したすべてのストリームが失われた場合に復旧できないという点です。残念ながらこのような互換性の問題のために、現実にはストリームはそれほど魅力的な機能ではなくなっています。ただし、NTFS ボリュームでのみ稼働するように設計されたサーバー側アプリケーションにとっては、ストリームは優れたツールであり、これを活用することで有意義なソリューションを構築できます。

ストリームの基本

非 NTFS ボリュームに複数ストリーム ファイルをコピーすると、メイン ストリームのみがコピーされます。この場合、それ以外のデータは失われてしまいます。なぜなら、そのファイルを NTFS ディスクにコピーして戻しても、それらのデータは復旧されないからです。ここでは、NTFS マシンでのみ作業する場合を想定して、名前付きストリームの作成方法を説明します。 サンプル コード 1 には、NTFS ファイルからストリームの読み取りと書き込みを行う WSH (Windows スクリプト ホスト) の VBScript (Microsoft Visual BasicR Scripting Edition) ファイルが示されています。

ファイル内で名前付きストリームを識別するには、特定の名前付け規則に従って、ファイル名の末尾にコロンとストリーム名を付加します。たとえば、 "test.txt" というファイルの "VersionInfo" というストリームにアクセスするには、次のファイル名を使用します。

   Test.txt:VersionInfo

この名前は、ファイルを操作するすべての Microsoft Win32(R) API 関数で使用します。 VersionInfo ストリームの内容にアクセスするには、この名前を "CreateFile()" に渡してから "ReadFile()" および "WriteFile()" を使って、通常どおりに読み取りと書き込みを行います。ファイル内にあるストリームが存在するかどうかを確認するには、ここで示したようにファイル ストリーム名を構成し、 "CreateFile()" を使用して存在するかどうかを確認します。

HANDLE hfile = CreateFile(szFileStreamName, GENERIC_READ, 0, 
   NULL, OPEN_EXISTING, 0, 0);
CloseHandle(hfile);
if (hfile == NULL)
   MessageBox(hWnd, "Error", NULL, MB_OK);

C++ プログラマのみがストリームを使用できるというわけではありません。 サンプル コード 1 に示しているように、Visual Basic やスクリプト コードでもストリームは活用できます。このような透過性を実現している主な要因は、 CreateFile() を始めとする低レベルの Win32 API 関数すべてが、NTFS パーティション上のストリームベースのファイル名をサポートしていることです。Windows 98 マシンなどの 非 NTFS パーティションで "Test.txt:VersionInfo" という名前のファイルを開こうとすると、"ファイルが見つかりません" というエラー メッセージが表示されます。注目したいのは、ファイルを含んでいるボリュームのファイル システムのみが問題であって、呼び出し側アプリケーションをホストする Windows プラットフォームやディスク パーティションの種類は関係ないということです。つまり、NTFS パーティション上の共有フォルダにある名前付きストリームには、接続されている Windows 98 マシンからでも問題なくアクセスできます。さらに、長いファイル名の場合でも、コロンは有効な文字ではありません。したがって、 CreateFile() はファイル名の中にコロンを見つけると、それに特別な意味があることを認識します。

サンプル コード 1 のように、ストリームは VBScript でも使用できます。これは、FileSystemObject オブジェクト モデルがファイルを開いたり、ファイルの書き込み、作成、およびテストを行う際に、 CreateFile() を集中的に利用するためです。サンプル コードでは、データのない長さがゼロのメイン ストリームと、任意の数の名前付きストリームを含んだテキスト ファイルを作成します。デモを実行して、ストリームをいくつか作成してみてください。それらのストリームを仮に、 "VersionInfo" および "VersionInfoEx" と呼びましょう。ファイル内にストリームが存在するかどうかは、Windows シェルからはわかりません。図 2 は、test.txt ファイルが Windows エクスプローラでどのように表示されるかを示しています。

ms810604.ntfs02(ja-jp,MSDN.10).gif

図 2 長さがゼロでも名前付きストリームを含んでいる可能性のあるファイル

[サイズ] 列には、名前のないメイン ストリームのサイズのみが表示され、 [プロパティ] ダイアログ ボックスでもストリームに関する詳細情報を得ることはできません。Windows 2000 の [プロパティ] ダイアログ ボックスでは、NTFS ボリューム上に限って、テキスト ファイルを含むすべてのファイルに要約情報を関連付けることができます。図 3 のように [概要] タブをクリックし、作成者などを入力します。

ちなみに、Windows 2000 ではシェル UI が拡張されたため、[作者] 列にこのような名前を表示することができます。これについては、 https://msdn.microsoft.com/msdnmag/ にある "MSDN Magazine" 誌 (英語) のプレミア版を参照してください。

ms810604.ntfs03(ja-jp,MSDN.10).gif

図 3 NTFS ボリューム上の .txt ファイルに追加情報を関連付ける

でも、ちょっと待ってください。要約情報は Word や Excel のドキュメントに設定する一般的なデータですが、ドキュメント自体に組み込まれています。テキスト ファイルの場合、その単純な内容を変えずにどうやって要約情報を関連付けるのでしょうか。簡単なことです。シェルがストリームによってそれを実現します。ひととおり変更を行ったら、ファイルを別の非 NTFS パーティションにコピーしてみてください。図 4 に示すダイアログ ボックスが表示されます。

ms810604.ntfs04(ja-jp,MSDN.10).gif

図 4 ストリーム データ損失に関する Windows 2000 からの警告

text.txt ファイルに、ドキュメント要約情報を含んだストリームが含まれていることがわかります。Windows 2000 は、追加情報を持つファイルが、それをサポートしないボリュームにコピーされようとしていることを認識しているのです。非 NTFS パーティションでは、名前のないメイン ストリームのみがコピーされ、残りのストリームは破棄されます。このため、ストリームベースのファイルを、それに対応しないシステムとやりとりすることは実際にはありません。

ストリームのバックアップと列挙

ファイルに含まれるすべてのストリームを列挙する方法、つまり API 関数はあるのでしょうか。あることはありますが、使いやすいものではありません。Win 32 のバックアップ用 API 関数 (BackupReadBackupWrite など) を使って、ファイル内のストリームを列挙することもできます。ただし、これは多少変わった使い方であり、効果的かつ最終的な解決策というよりは、むしろ回避策といえます。

要するに、ファイルまたはフォルダ全体をバックアップする場合は、すべての情報をひとまとめにして格納する必要があります。こうした理由から、ファイルのストリームを列挙するには、 BackupRead() を使用するのが最適です。この関数のプロトタイプを見てみましょう。

BOOL BackupRead(
  HANDLE hFile,          
  LPBYTE lpBuffer,        
  DWORD nNumberOfBytesToRead,  
  LPDWORD lpNumberOfBytesRead,  
  BOOL bAbort,                  
  BOOL bProcessSecurity,        
  LPVOID *lpContext             
);

便宜上、ここではコンテキストやセキュリティなどを無視します。 hFile 引数は、 CreateFile() を呼び出して取得する必要があります。一方 lpBuffer は、 WIN32_STREAM_ID データ構造体を指し示していなければなりません。

typedef struct _WIN32_STREAM_ID { 
    DWORD         dwStreamId; 
    DWORD         dwStreamAttributes; 
    LARGE_INTEGER Size; 
    DWORD         dwStreamNameSize; 
    WCHAR         cStreamName[ANYSIZE_ARRAY]; 
} WIN32_STREAM_ID, *LPWIN32_STREAM_ID;

このような構造体の最初の 20 バイトは、各ストリームのヘッダーを表します。ストリームの名前は、 dwStreamNameSize フィールドの直後から始まり、名前の後にはストリームの内容が続きます。ファイルの従来の内容はストリーム (ただし名前のないストリーム) として見ることができるため、すべてのストリームを列挙するには、 BackupRead が False を返すまでループするだけです。事実、 BackupRead は、特定のファイルやフォルダに関連付けられたすべての情報を読み取るためのものと考えられています。

WIN32_STREAM_ID sid;
ZeroMemory(&sid, sizeof(WIN32_STREAM_ID));
DWORD dwStreamHeaderSize = (LPBYTE)&sid.cStreamName - 
      (LPBYTE)&sid+ sid.dwStreamNameSize;
bContinue = BackupRead(hfile, (LPBYTE) &sid, 
   dwStreamHeaderSize, &dwRead, FALSE, FALSE, 
   &lpContext);

上記の部分は、ストリームのヘッダーを読み取る非常に重要なコードです。この処理に成功したら、実際のストリーム名を読み取る処理を実行できます。

WCHAR wszStreamName[MAX_PATH]; 
BackupRead(hfile, (LPBYTE) wszStreamName, sid.dwStreamNameSize, 
   &dwRead, FALSE, FALSE, &lpContext);

次のストリームに取りかかる前に、 BackupSeek() を呼び出してバックアップ ポインタを前に進めておきます。

BackupSeek(hfile, sid.Size.LowPart, sid.Size.HighPart, 
   &dw1, &dw2, &lpContext);

ほとんどの場合、ストリームは通常のファイルと同じように処理できます。たとえば、ストリームを削除するには、 DeleteFile() を使用します。ストリームの内容を更新する場合は、 ReadFile()WriteFile() を使用します。ストリームを移動または名前変更するための正式な方法はサポートされていません。この記事の最後の部分では、このコードを利用して、ストリーム情報を持つすべてのファイルに新しいプロパティ ページを追加する NTFS 2000 固有の Windows シェル拡張を構築します。その前に、もう 1 つの NTFS 機能について簡単に説明します。

ハード リンク

ショートカットについてご存じでしょうか。デスクトップに散らばっている、何かを参照するあの小さな .lnk ファイルです。確かにショートカットは便利な機能ですが、いくつかの欠点もあります。まず、異なるフォルダにある同じ対象を複数のショートカットでポイントしている場合は、同じファイルのコピー (幸い小さなファイルですが) を複数持っていることになります。もっと重要なのは、ショートカットの対象オブジェクトが、その後変更される可能性があることです。移動や削除が行われたり、単に名前が変更されるかもしれません。このとき、ショートカットはどうなるのでしょうか。それらの変更を検出し、追跡し、(自動的に) 正しく更新されるのでしょうか。残念ながらそれは無理です。その主な理由は、ショートカットがアプリケーションレベルの機能であることです。システムから見れば、ショートカットは開くときに多少の余分な作業を伴うユーザー定義ファイルに過ぎません。ショートカットにする特権は、ほかのクラスのファイルにも割り当てられることを考えてみてください。これが理に適っていれば、.lnk 以外の拡張子でショートカットのクラスを独自に作成することができます。この作業を行うのは、クラス ノードの下にある "IsShortcut" というレジストリ エントリです。たとえば .xyz ファイルをショートカットとして扱うとします。 HKEY_CLASSES_ROOT の下に .xyz ノードを作成することでファイル クラスを登録し、これが別のノード、通常は "xyzfile" をポイントするようにします。そして空の REG_SZ エントリを次に追加します。

HKEY_CLASSES_ROOT
\xyzfile

これで完了です。

ほかのオペレーティング システム、特に Posix と OS/2 には、システム レベルで機能する同様の機能があります。特に OS/2 ではこの機能を "shadows" と呼びます。ハード リンクは、あるファイルに対するファイル システム レベルのショートカットです。既存のファイルへのハード リンクを作成する場合、そのファイルおよびそのファイルに対するファイルベースの参照 (つまりショートカット) は複製しません。代わりに、NTFS レベルでそのファイルのディレクトリ エントリに情報を追加します。物理的なファイルは、元の場所に置いたまま一切手を加えません。簡単に言うと、このファイルは複数の名前を持つことになり、ユーザーはそれらを使って同じ内容にアクセスすることができます。

ハード リンクを使用すると、単一の物理的な内容にアドレスするさまざまなパス名をシステムが管理してくれるので、同じファイルの複数の (必要な) コピーを管理する必要がなくなります。これにより作業は大幅に簡素化され、貴重なディスク領域を節約できます。そのうえ、ハード リンクはシステムレベルのショートカットなので、ファイル名を変更したりファイルを移動しても、常に正しい対象ファイルをポイントします。ハード リンクはファイル システム レベルで格納されるため、すべての変更が自動的かつ透過的に反映されます。ハード リンクは、同じ NTFS ボリューム内で作成しなければならないので注意が必要です。たとえば、ドライブ D: のファイルをポイントするハード リンクをドライブ C: に作成することはできません。

エイリアスの方がわかりやすければ、ハード リンクをファイルのエイリアスと考えてかまいません。ファイルには任意のエイリアスを使ってアクセスすることができ、そのエイリアスがすべて削除されなければファイルを削除することはできません (エイリアスが参照カウントとして機能します)。ハード リンクはエイリアスであるため、内容の同期化を考える必要はありません。

CreateHardLink() は、ハード リンクを作成するための API 関数です。この関数のプロトタイプは次のようになります。

BOOL CreateHardLink(
  LPCTSTR lpFileName,                          
  LPCTSTR lpExistingFileName,               
  LPSECURITY_ATTRIBUTES lpSecurityAttributes  
);  

以前の "MIND" 誌の記事 ("MIND" 誌 1999 年 3 月号 (「Windows 2000 for Web Developers (英語) 」を参照))で、スクリプト コードからハード リンクを作成する COM オブジェクトをサンプル コードとして添付しました。 サンプル コード 2 は、このコードを利用して特定のファイルへのハード リンクを作成する VBScript プログラムです。ファイルのハード リンクの数は簡単にわかりますが、それらをすべて列挙する機能はありません。この情報を得るには、API 関数 GetFileInformationByHandle()BY_HANDLE_FILE_INFORMATION 構造体に情報を書き込み、その構造体の nNumberOfLinks フィールドを参照します。リンクされたファイルの名前をすべて列挙するのは、もう少し難しくなります。基本的には、ボリューム全体をスキャンし、ファイルごとに割り当てられているユニークな ID を追跡する必要があります。既存の ID に行き当たれば、そのファイルのハード リンクを見つけたことになります。ファイルの一意な ID はシステムによって割り当てられ、 BY_HANDLE_FILE_INFORMATIONnFileIndexHigh および nFileIndexLow フィールドに格納されます。

NTFS の機能を享受する

ストリームが特に役立つとされるのは、ファイルの元の形式を変えたり壊すことなく、またディスク領域を占有することなくファイルに情報を追加できる点です。もちろん、ストリームはそれ自体の領域を必要としますが、Windows エクスプローラはその領域を認識しません。Windows エクスプローラからはストリームが見えないため、空きディスク領域がたくさんあるように見えても、実際は空き領域が深刻なほど少ない可能性もあります。(不可視の) 追加情報は、テキスト ファイルや実行可能ファイルを含むどのファイルにも追加できます。

一方ハード リンクは、共有情報を集中化するための優れたリソースです。用意されるのは、さまざまなパスからアクセスされる情報用のリポジトリ 1 つだけです。ハード リンクは、Windows NT テクノロジのまったく新しい概念というわけではありません。ハード リンクは、Windows NT の誕生当時から存在しましたが、ハード リンクを作成するパブリック関数を Microsoft が提供したのは Windows 2000 が最初です。各ファイルは、自分自身へのリンクを少なくとも 1 つは持っているため、 GetFileInformationByHandle は必ずゼロより大きなリンク数を返します。ディレクトリにハード リンクを設定することはできません。設定できるのはファイルだけです。

ストリームとハード リンクに共通する現実的な問題は、シェルからのサポートがきわめて限られているという点です。これに対処するため、筆者は特定のファイルのストリームとハード リンクに関する情報を提供するシェル拡張を作成しました。図 5 はこの機能の外観を示しています。

ms810604.ntfs05(ja-jp,MSDN.10).gif

図 5 ストリームとハード リンクに関する情報を表示する [Streams] タブ

このシェル拡張のソース コードでは、API 関数 BackupRead() を使用してストリームを列挙しています。選択したストリームの内容は、 **DeleteFile()**を呼び出して削除しています。 [Edit Streams] ボタンをクリックすると、 サンプル コード 1 のスクリプト コードが実行され、ストリームの追加と更新が可能となります。同様に、 [Create Hard Link] ボタンをクリックすると、サンプル コード 2 のコードが実行され、リンクが新たに作成されます。このユーザー インターフェイスでは、更新を実行して初めてすべての変更が反映されます。最後に、ハード リンクを削除 (つまりファイルを削除) しても、削除されたファイルがごみ箱に残っている限り、リンクの総数は更新されないので注意してください。

まとめ

この記事では、ストリームやハード リンクなどの主要機能を中心に、NTFS 2000 について表面的に説明しました。Windows 2000 ファイル システムの新機能をもっと幅広くとらえるには、Jeff Richter と Luis CabreraI による "MSJ"誌 1998 年 9 月号の記事「A File System for the 21st Century: Previewing the Windows NT 5.0 File System (英語)」 (https://www.microsoft.com/msj/1198/ntfs/ntfs.aspx Non-MSDN Online link) を一読することをお勧めします。スパース ストリームや再解析ポイントなどの興味深い話題については今回触れませんでしたが、この記事を面白く読んでいただいた方はご連絡ください。すぐに続編を準備します。

サンプル コード 1

' CreateStream.vbs による
' NTFS ボリュームでのストリームのデモ 
' --------------------------------------------------------

Option Explicit

' 定数
Const L_NotNTFS = "現在のボリュームは NTFS ではありません。"
Const L_EnterFile = "ファイル名を入力してください。"
Const L_TestNTFS = "NTFS のテスト"
Const L_StdFile = "c:\testntfs\test.txt"
Const L_EnterStream = "ストリーム名を入力してください。"
Const L_StdStream = "VersionInfo"
Const L_EnterTextStream = "ストリームのテキストを入力してください。"
Const L_StdContent = "1.0"

' 現在のボリュームが NTFS であることを確認する
if Not IsNTFS() then 
   MsgBox L_NotNTFS
   WScript.Quit
end if

' ファイル名に対してクエリを実行する
dim sFileName
sFileName = InputBox(L_EnterFile, L_TestNTFS, L_StdFile)
if sFileName = "" then WScript.Quit

' 書き込まれるストリームに対してクエリを実行する
dim sStreamName
sStreamName = InputBox (L_EnterStream, L_TestNTFS, L_StdStream)
if sStreamName = "" then WScript.Quit

' FS オブジェクト モデルを初期化する
dim fso, bExist
set fso = CreateObject("Scripting.FileSystemObject")   

' ファイルを作成する (存在しない場合)
dim ts
if Not fso.FileExists(sFileName) then 
   set ts = fso.CreateTextFile(sFileName)
   ts.Close
end if 

' ストリームの現在の内容を読み込む
dim sFileStreamName, sStreamText
sFileStreamName = sFileName & ":" & sStreamName
if Not fso.FileExists(sFileStreamName) then 
   sStreamText = L_StdContent
else
   set ts = fso.OpenTextFile(sFileStreamName)
   sStreamText = ts.ReadAll()
   ts.Close
end if 

' 書き込まれるストリームの内容に対してクエリを実行する
sStreamText = InputBox (L_EnterTextStream, L_TestNTFS, sStreamText)
if sStreamText = "" then WScript.Quit

' ストリームに書き込む
set ts = fso.CreateTextFile(sFileStreamName)
ts.Write sStreamText


' アプリケーションを終了する
set ts = Nothing
set fso = Nothing
WScript.Quit




' ////////////////////////////////////////////////////////
' // ヘルパー関数

' IsNTFS() - 現在のボリュームが NTFS であるかどうかを確認する
' --------------------------------------------------------
function IsNTFS()
   dim fso, drv
   
   IsNTFS = False
   set fso = CreateObject("Scripting.FileSystemObject")   
   set drv = fso.GetDrive(fso.GetDriveName(WScript.ScriptFullName)) 
   set fso = Nothing
   
   if drv.FileSystem = "NTFS" then IsNTFS = True
end function

サンプル コード 2

' Hardlinks.vbs
' NTFS ボリュームでのハード リンクのデモ
' --------------------------------------------------------

Option Explicit

' 定数
Const L_NoHardLinkCreated = "ハード リンクを作成できません。"
Const L_EnterTarget = "ハード リンク先のファイル名を入力してください。"
Const L_HardLinks = "ハード リンクの作成中"
Const L_EnterHardLink = "作成するハード リンクの名前"
Const L_CannotCreate = "両方のファイルが同じボリューム上にあり、そのボリュームが NTFS であることを確認してください。"
Const L_NotExist = "ファイルは存在しません。"
Const L_SameName = "ターゲット ファイルとハード リンクの名前を同じにすることはできません。"

' (ハード) リンクを設定する既存のファイルを決定する
dim sTargetFile 
if WScript.Arguments.Count >0 then
   sTargetFile = WScript.Arguments(0)
else
   sTargetFile = InputBox(L_EnterTarget, L_HardLinks, "")
   if sTargetFile = "" then WScript.Quit
end if

' ファイルが存在するかどうかを確認する
dim fso
set fso = CreateObject("Scripting.FileSystemObject")   
if Not fso.FileExists(sTargetFile) then
   MsgBox L_NotExist
   WScript.Quit
end if

' メイン ループ
while true
   QueryForHardLink sTargetFile
wend


' 終了
WScript.Quit






' /////////////////////////////////////////////////////////////
' // ヘルパー関数



' ハード リンクを作成する
'------------------------------------------------------------
function QueryForHardLink(sTargetFile)
   ' コマンド ラインで指定された場合にハード リンク名を取得する
   dim sHardLinkName
   if WScript.Arguments.Count >1 then
      sHardLinkName = WScript.Arguments(1)
   else
      dim buf
      buf = L_EnterHardLink & " for" & vbCrLf & sTargetFile
      sHardLinkName = InputBox(buf, L_HardLinks, sTargetFile)
      if sHardLinkName = "" then WScript.Quit   
      if sHardLinkName = sTargetFile then 
         MsgBox L_SameName
         exit function
      end if
   end if 

   ' 両ファイルが同じボリュームにあるか、
   'そのボリュームが NTFS であるかを確認する
   if Not CanCreateHardLinks(sTargetFile, sHardLinkName) then 
      MsgBox L_CannotCreate
      exit function
   end if
   
   ' ハード リンクを作成する
   dim oHL
   set oHL = CreateObject("HardLink.Object.1")
   oHL.CreateNewHardLink sHardLinkName, sTargetFile
end function


' 両方のファイルが同じ NTFS ディスクにあるかどうかを確認する
'------------------------------------------------------------
function CanCreateHardLinks(sTargetFile, sHardLinkName)
   CanCreateHardLinks = false
   
   dim fso
   set fso = CreateObject("Scripting.FileSystemObject")
   
   ' 同じドライブかどうかを確認する
   dim d1, d2
   d1 = fso.GetDriveName(sTargetFile)
   d2 = fso.GetDriveName(sHardLinkName)
   if d1 <> d2 then exit function

   ' NTFS ドライブであるかどうかを確認する
   CanCreateHardLinks = IsNTFS(sTargetFile)
end function


' IsNTFS() - ファイルのボリュームが NTFS であるかどうかを確認する
' --------------------------------------------------------
function IsNTFS(sFileName)
   dim fso, drv
   
   IsNTFS = False
   set fso = CreateObject("Scripting.FileSystemObject")   
   set drv = fso.GetDrive(fso.GetDriveName(sFileName)) 
   set fso = Nothing
   
   if drv.FileSystem = "NTFS" then IsNTFS = True
end function