Cloud Filter APIで同期失敗時にエクスプローラーのエラーアイコンを確実に表示する方法|STATUS_CLOUD_FILEとNTSTATUSの正しい使い分け

Windows の仮想ファイルシステム(VFS)を Cloud Filter API で実装していると、ネットワーク断や資格情報の失効などで OnFetchData が失敗する場面は避けられません。ところが CfExecute に汎用の E_FAIL などを入れても、エクスプローラーの「同期ステータス」列に“エラー”アイコンが出ない——本稿はその理由と、正しくエラーアイコンを点灯させるための実装・運用ノウハウを、コードと検証手順まで踏み込んで解説します。

目次

問題の本質:Cloud Filter は NTSTATUS を見ている

Cloud Filter API のコールバック(例:CF_CALLBACK_TYPE_FETCH_DATA)に対する応答は CfExecute で完了させます。このときエクスプローラーの同期ステータス(列表示やオーバーレイアイコン)に反映されるのは、汎用 HRESULT ではなく、ntstatus.h に定義された STATUS_CLOUD_FILE_* 系の NTSTATUS です。

汎用の失敗(例:E_FAIL、HRESULT_FROM_WIN32(ERROR_…))を CompletionStatus に渡すと、クラウドファイルドライバー側で一般化され、ユーザー可視の「同期エラー」イベントとしては扱われません。結果、UI は「エラー」を点灯せず、ユーザーは何が起きたか把握できない状態に陥ります。

結論:STATUS_CLOUD_FILE_* を直接返す

対策はシンプルです。CfExecute に渡す CF_OPERATION_PARAMETERS.TransferData.CompletionStatus に、状況に応じた STATUS_CLOUD_FILE_* を設定します。これにより、エクスプローラーは失敗理由を「同期エラー」として正しく分類し、赤バツや感嘆符のアイコンを表示します。

失敗を明示する最小コード例(C++)

// コールバック: CF_CALLBACK_TYPE_FETCH_DATA(OnFetchData 相当)
void OnFetchData(const CF_CALLBACK_INFO* info, const CF_CALLBACK_PARAMETERS* params) {
    CF_OPERATION_INFO opInfo = {};
    opInfo.StructSize    = sizeof(opInfo);
    opInfo.Type          = CF_OPERATION_TYPE_TRANSFER_DATA;
    opInfo.ConnectionKey = info->ConnectionKey;
    opInfo.TransferKey   = info->TransferKey;
CF_OPERATION_PARAMETERS opParams = {};
opParams.ParamSize = sizeof(opParams);

// ここでは敢えて失敗を返してエラーアイコンを出す
// 例:バックエンドが停止中で、データ取得不可
opParams.TransferData.CompletionStatus = STATUS_CLOUD_FILE_PROVIDER_NOT_RUNNING;

// 失敗時は Buffer/Offset/Length は参照されない(安全のため 0 にしておく)
opParams.TransferData.Offset = 0;
opParams.TransferData.Length = 0;
opParams.TransferData.Buffer = nullptr;

HRESULT hr = CfExecute(&opInfo, &opParams);
// CfExecute 自体の戻り値(HRESULT)は API 呼び出し成否。UI 反映は CompletionStatus の NTSTATUS で決まる。
(void)hr;

} 

ポイントは、「失敗の意味」を持つ STATUS_CLOUD_FILE_* を直接設定することです。以降で代表的なステータスと使い所を整理します。

代表的な STATUS_CLOUD_FILE_* と使いどころ

以下は実運用で利用頻度の高いステータスの一例です。プロバイダーの失敗理由と UI の出方を一致させるため、状況に最も近いものを選びましょう。

意味NTSTATUS コード想定シナリオUI(一般的な挙動)
プロバイダーが停止・再起動中STATUS_CLOUD_FILE_PROVIDER_NOT_RUNNINGバックエンドサービス落ち/更新適用中同期エラー(赤バツ/感嘆符)
ネットワーク不通STATUS_CLOUD_FILE_NETWORK_UNAVAILABLEオフライン、プロキシ障害、DNS 障害など同期エラー(再試行で復旧可)
アクセス拒否STATUS_CLOUD_FILE_ACCESS_DENIEDトークン失効/権限不足/ポリシー違反同期エラー(ユーザー対応が必要)
一時的な競合/利用不可STATUS_CLOUD_FILE_BUSY 等同時アクセス、サーバー側ロック同期エラー(時間をおいて再試行)
対象が同期ルート外STATUS_CLOUD_FILE_NOT_UNDER_SYNC_ROOT無効なマウントや移動直後同期エラー(設定見直し要)

上記は一例です。ntstatus.h には他にもクラウドファイル専用のコードが定義されています。固有のエラー分類がある場合は最も意味の近いものを選び、UI のメッセージとユーザーの次アクションが直感できる状態に整えましょう。

「E_FAIL を返したのに出ない」理由の分解

  • 評価対象が違う:Cloud Filter ドライバーは、CfExecute の CompletionStatus(NTSTATUS)を UI 反映ロジックのキーにしています。HRESULT の値はそのまま「同期エラー」状態の判定には使われません。
  • 一般化される:汎用の失敗値を渡すと、ドライバー内でより抽象度の高い失敗に丸められ、ユーザー可視の「同期エラー」には直結しないことがあります。
  • 分類できない:「何のエラーか」を STATUS_CLOUD_FILE_* で伝えないと、UI は具体的な表示(赤バツ・感嘆符・列ラベル)にマッピングできません。

実装テンプレート:例外から STATUS_CLOUD_FILE_* へマップする

バックエンド SDK の例外や Win32 エラーを、そのまま CompletionStatus に流し込むのは避け、必ず「クラウドファイル文脈の NTSTATUS」に正規化します。

#include <ntstatus.h>
#define WIN32_NO_STATUS
#include <windows.h>

// バックエンド例外/Win32 エラーから Cloud File 用 NTSTATUS へマップ
NTSTATUS MapToCloudStatus(const std::exception& ex, DWORD lastError) {
    // 1) ネットワークが原因
    if (/* DNS 失敗/タイムアウト/プロキシ不可 等の判定 */) {
        return STATUS_CLOUD_FILE_NETWORK_UNAVAILABLE;
    }
    // 2) 認証・認可
    if (lastError == ERROR_ACCESS_DENIED || /* token expired */) {
        return STATUS_CLOUD_FILE_ACCESS_DENIED;
    }
    // 3) プロバイダー停止/内部状態不整合
    if (/* service not running / restarting */) {
        return STATUS_CLOUD_FILE_PROVIDER_NOT_RUNNING;
    }
    // 4) 一時的な競合
    if (lastError == ERROR_SHARING_VIOLATION || lastError == ERROR_LOCK_VIOLATION) {
        return STATUS_CLOUD_FILE_BUSY;
    }
    // デフォルト(「同期エラー」として扱わせる汎用クラウド失敗)
    return STATUS_CLOUD_FILE_UNSUCCESSFUL; // クラウドファイル領域の一般失敗
}

そしてコールバック内でこのマッピングを経由して CfExecute に渡します。

void OnFetchData(const CF_CALLBACK_INFO* info, const CF_CALLBACK_PARAMETERS* params) {
    CF_OPERATION_INFO opInfo = { sizeof(opInfo) };
    opInfo.Type          = CF_OPERATION_TYPE_TRANSFER_DATA;
    opInfo.ConnectionKey = info->ConnectionKey;
    opInfo.TransferKey   = info->TransferKey;

    CF_OPERATION_PARAMETERS opParams = { sizeof(opParams) };

    try {
        // ... バックエンドからデータ取得し、成功パスではバッファを渡す ...
        // ここでは失敗パス例
        throw std::runtime_error("backend unavailable");
    }
    catch (const std::exception& ex) {
        NTSTATUS st = MapToCloudStatus(ex, GetLastError());
        opParams.TransferData.CompletionStatus = st;
        opParams.TransferData.Offset = 0;
        opParams.TransferData.Length = 0;
        opParams.TransferData.Buffer = nullptr;
        CfExecute(&opInfo, &opParams);
        return;
    }
}

呼び出しの前提条件:スレッド・権限・ハンドル

  • 同一セッション/適切な権限:コールバックが通知されたコンテキスト(接続キー/転送キー)と整合するスレッドから CfExecute を呼び出します。プロセスを跨いで投げる場合は、キーの受け渡しとセキュリティコンテキストの整合性に注意してください。
  • ハンドル属性:一時ファイル属性(FILE_ATTRIBUTE_TEMPORARY)や非互換の共有モードでプレースホルダーにアクセスすると、ドライバーが失敗扱いにすることがあります。標準的な読み取り共有(FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE)を推奨します。
  • 失敗時は空データでよい:失敗を返すケースでは、Buffer/Length を 0 にして CompletionStatus のみを意味のある値にするのが安全です。

UI のふるまい:エラーの出方と復旧

  • 即時反映:クラウドファイルのエラーは多くの場合、対象アイテムの「同期ステータス」列とオーバーレイに即時反映されます。
  • 復旧の合図:次回の成功フェッチが完了したら、CfSetInSyncState で In Sync を明示し、UI のエラーを消します。ピン状態(CfSetPinState)は UI の「エラー」表示に影響しません。
  • 継続的な失敗:同一ファイルで同じ STATUS_CLOUD_FILE_* が繰り返されると、ユーザーは恒常エラーと認識します。リトライ方針(指数バックオフなど)とユーザー通知方針(後述)を揃えておきましょう。

ユーザー通知の設計:最小限で最大の理解を

STATUS_CLOUD_FILE_* を返すだけで UI は「同期エラー」を示しますが、原因の詳細はユーザーに伝わりません。サポート負荷を下げるため、以下のいずれかで補助通知を用意しておくと効果的です。

  • シェル拡張のトースト/バルーン:ネットワーク断・認証切れ・プロバイダー停止など、解決アクションが異なるものは個別にメッセージを出す。
  • コンテキストメニューの「再試行」:対象ファイルに対する明示的な再同期を提供。
  • 診断 ID の表示:内部ログの相関キー(要求 ID やトレース ID)を UI に控えめに表示しておく。

デバッグの実践:ETW と ProcMon を組み合わせる

ETW(Cloud Files プロバイダー)

ETW の「Cloud Files」プロバイダー(例:EEE2665A-3B13-4D2F-9B39-4CFCF3549198)を有効にし、CfExecute に渡した CompletionStatus とドライバーの解釈をイベントで確認します。収集は wpr または logman で手早く始められます。

# 例:管理者 PowerShell
wpr -start CloudFiles -filemode
# 再現操作を実施
wpr -stop .\CloudFilesTrace.etl

Process Monitor(ProcMon)

フィルターで対象プロセス(エクスプローラー/プロバイダー)を絞り、IRP_MJ_FILE_SYSTEM_CONTROL の FSCTL_CLOUD_* 操作に注目します。Result に NTSTATUS が現れるため、ドライバーがどのコードを受け取り、どう返したかが手に取るように分かります。

ProcMon フィルター例条件
Process Nameexplorer.exe / プロバイダーの実装プロセス
OperationFileSystemControl
DetailFSCTL_CLOUD_* を含む
Result例:STATUS_CLOUD_FILE_NETWORK_UNAVAILABLE

ケーススタディ:よくある失敗と対処フロー

ネットワーク断(オフライン)

  • 返すコード:STATUS_CLOUD_FILE_NETWORK_UNAVAILABLE
  • UI:エクスプローラーは「同期エラー」を表示。オンライン復帰後に再試行で自動復旧。
  • 実装:ネットワーク検出の結果をキャッシュし、短周期のリトライ嵐を避ける。

資格情報の失効(再認証が必要)

  • 返すコード:STATUS_CLOUD_FILE_ACCESS_DENIED
  • UI:「同期エラー」表示。ユーザーはサインイン操作が必要。
  • 実装:UI 通知で「再サインイン」導線を出し、成功後は CfSetInSyncState でエラー解除。

バックエンドの計画停止(メンテナンス)

  • 返すコード:STATUS_CLOUD_FILE_PROVIDER_NOT_RUNNING
  • UI:「同期エラー」表示。
  • 実装:メンテナンス時間帯はフェッチを抑止。ユーザーには事前通知、再開後に差分同期を優先処理。

正しく伝えるための UI 文言とテレメトリ

  • ユーザーメッセージ:「何が起きたか」(ネットワーク/認証/サービス停止)と「何をすればよいか」(接続確認/再サインイン/しばらく待つ)を 1 行で伝える。
  • テレメトリ:返却した STATUS_CLOUD_FILE_*、対象パス、リトライ回数、遅延、ユーザー操作(再試行/キャンセル)を記録。

品質向上のためのチェックリスト

  • NTSTATUS の徹底:CompletionStatus に 必ず STATUS_CLOUD_FILE_* を渡している。
  • 意味の一貫性:同じ原因に同じコードを返し、ユーザー体験の一貫性を保っている。
  • 復旧動線:エラー後のユーザー導線(再サインイン/再試行)が UI に用意されている。
  • ETW ログ:再現プロセスがドキュメント化され、誰でも ETW で裏取りできる。
  • ProcMon 検証:FSCTL の往復で期待どおりの NTSTATUS が見えている。
  • 成功時の後始末:フェッチ成功後に CfSetInSyncState で UI を健全化。

応用:部分レンジとリトライ戦略

大型ファイルのオンデマンド展開では、部分レンジごとに成功/失敗が混在することがあります。ユーザー視点では「一度でもエラーが出たら赤バツ」という見え方になるため、以下の方針を推奨します。

  • 致命的エラーは即時失敗:認証失効やパス不正など、再試行しても意味のないものは早く失敗にする。
  • 一時エラーはバックオフ:ネットワーク不安定やサーバー過負荷は指数バックオフで数回だけ再試行し、それでもだめなら STATUS_CLOUD_FILE_BUSY 等で明示失敗。
  • 成功レンジの維持:成功したレンジは CF_OPERATION_TRANSFER_DATA_FLAG_NONE で確定させ、再試行では未取得レンジのみ要求する。

テスト観点:自動・手動の両輪で

観点具体的テスト期待される UI
ネットワーク断NIC 無効化・プロキシ強制・DNS 失敗同期エラー(復旧で自動回復)
認証失効トークン期限切れ・権限剥奪同期エラー(再サインイン導線)
バックエンド停止サービス停止・リサイクル同期エラー(時間経過で回復)
競合同時書き込み・ロック同期エラー(再試行で成功)

実装ミスの早見表

症状よくある原因対処
UI にエラーが出ないE_FAIL など HRESULT を渡しているSTATUS_CLOUD_FILE_* に置き換える
まれに成功扱いになる成功パスで NTSTATUS=0(STATUS_SUCCESS)を誤って返す失敗パスは必ず非 0 のクラウド系コード
ProcMon に意図しないコード例外 → Win32 → HRESULT → NTSTATUS の多段変換変換を止め、直接 STATUS_CLOUD_FILE_* を返す
スレッドで不定期に失敗コールバックと別セッション/不整合なトークン同一セッションで CfExecute、権限整合を確認

サンプル:成功時の完了と In-Sync 更新

void CompleteFetchSuccess(const CF_CALLBACK_INFO* info,
                          const void* buffer, size_t size, LARGE_INTEGER offset) {
    CF_OPERATION_INFO opInfo = { sizeof(opInfo) };
    opInfo.Type          = CF_OPERATION_TYPE_TRANSFER_DATA;
    opInfo.ConnectionKey = info->ConnectionKey;
    opInfo.TransferKey   = info->TransferKey;
CF_OPERATION_PARAMETERS opParams = { sizeof(opParams) };
opParams.TransferData.CompletionStatus = STATUS_SUCCESS;
opParams.TransferData.Offset           = offset.QuadPart;
opParams.TransferData.Length           = static_cast<LONGLONG>(size);
opParams.TransferData.Buffer           = const_cast<void*>(buffer);

CfExecute(&opInfo, &opParams);

// 全レンジ配信後(またはファイルクローズ時)に In-Sync を付ける
CF_IN_SYNC_STATE inSync = CF_IN_SYNC_STATE_IN_SYNC;
CfSetInSyncState(info->FileHandle, inSync, CF_SET_IN_SYNC_FLAG_NONE, nullptr);

} 

成功パスでは STATUS_SUCCESS を返し、必要に応じて CfSetInSyncState で同期済みを明示します。失敗パスとの対称性を保ち、UI の状態遷移が分かりやすくなります。

運用のヒント:ログ粒度とアラート

  • 集計基準:STATUS_CLOUD_FILE_* 別に発生件数を集計し、しきい値でアラート。
  • 相関 ID:プロセス起動・接続キー・転送キーに相関 ID を紐づけ、ETW と突合しやすくする。
  • ユーザー影響度:同一ファイルで 3 回連続失敗など、ユーザー影響の大きいパターンを別途可視化。

まとめ:汎用 HRESULT から脱却し、意味を返す

エクスプローラーの“エラー”アイコンを正しく出す鍵は、汎用 HRESULT をやめて、クラウドファイル専用の NTSTATUS を返すことです。STATUS_CLOUD_FILE_* の中から状況に合うものを選び、CfExecute の CompletionStatus に直接設定しましょう。表示が出ない場合は、呼び出しスレッド/権限/ハンドル属性の整合と、ETW/ProcMon で返却コードの実測を確認すれば、原因の切り分けは一気に進みます。

「ユーザーが次に何をすべきか」を UI で補助し、成功時には CfSetInSyncState で正常化——この一連の動線を整えるだけで、問い合わせは確実に減り、VFS 体験の満足度が上がります。今日から 意味のある NTSTATUS を返し、ユーザーも開発者も迷わない同期エラー表示を実現しましょう。

この記事を書いた人

実務の現場で詰まりがちなポイントを地図にするITブログ「IT trip」を運営。Windows/Office(Teams・Excel)からSQL、サーバ運用、ガジェットまで、再現性のある手順と“なぜそうなるか”を丁寧に解説します。読んだらすぐ試せること、そして迷った人の次の一歩が見えることを大切にしています。

コメント

コメントする

目次