には、コンテナごとにカーネルがある、Hyper-V コンテナと、そうでない Windows Server コンテナがある。後者について述べる。2016 Server でサポートされる。Docker API やコマンドで操作できる。もちろん、 Linux Docker イメージは、動かない。
出典
https://msdn.microsoft.com/ja-jp/windows/hardware/drivers/ifs/anti-virus-optimization-for-windows-containers - Anti-virus optimization for Windows Containers
コンテナはホストのファイルシステムを共有する。書き込むと、 copy on write がされる。これは、container isolation filter (wcifs.sys) が行う。Linux だと、aufs, overlayfs などの、ファイルシステムスタック技術か、デバイスマッパのシンプロビジョニングが使われるところ。
前述のアンチウイルスの記事は、例えば、ntoskrnl.exe は全てのコンテナが読むけど、スキャンは一度すればいいよね。ドライバはどうやってそれがわかる?という話。
カーネル一つに、ユーザランドが複数セットになる、というのは、リモートデスクトップでもあったので、それ自体に大きな問題はないのでない?
Altitudes for commercially released minifilter drivers will be assigned by Microsoft
フィルタ挿入位置は、申請しないといけない。
post-operation callback routines can be called at DPC
get context は、post op では呼べない。pre op で取って渡す。
FltCreateFile すると、Zw と違って、自分には再帰してこない
FLT_FILE_NAME_NORMALIZED_FORMAT, FLT_FILE_NAME_QUERY_DEFAULT を使うこと。
アプリが指定した8.3名を拾わなくてはいけない理由、フレームワークがキャッシュしてくれているファイルパス名を見てはいけない理由がないならば。
FltGetFileNameInformation() だけで、絶対パス名は得られる。parse は、最後の名前コンポーネントとか、親を区切りたい時だけする。wcschr "\" するのと変わらんのと違う?
FltGetIrpName 3は、"READ"、と返してくれる。
https://msdn.microsoft.com/en-us/library/windows/hardware/ff543083(v=vs.85).aspx
FltGetDestinationFileNameInformation rename 対象のパスを返す。Irp のパラメタをたどってくれるだけと思うけど。
違った。相対パスの親ハンドルを解決してくれるのがみそらしい。
https://msdn.microsoft.com/en-us/library/windows/hardware/ff543003(v=vs.85).aspx
PFLT_GENERATE_FILE_NAME オープンをフックして、ファイル名として自分の勝手なものを与えられるらしいが、つかいみちがわからない。 rename にもある。
https://msdn.microsoft.com/en-us/library/windows/hardware/ff551087(v=vs.85).aspx
FltRequestOperationStatusCallback を登録すると、oplock が取られたかを後から知ることができる。
Windows バージョンは、NTDDI_VERSION
readdir 相当は、
mj=IRP_MJ_DIRECTORY_CONTROL
mn=IRP_MN_QUERY_DIRECTORY
で、FileInformationClass によって、ファイル名以外の属性もついでに持っていく。
fastfat\dirctrl.c を参照。
OpenFileById
https://msdn.microsoft.com/en-us/library/windows/desktop/aa365432(v=vs.85).aspx
WDF ソースがMITライセンスで公開されていた。toaster も、WDFを使うようになおっていた。
https://github.com/Microsoft/Windows-driver-frameworks
ファイル システム トンネリング。ワードが、一時ファイルに新しいデータを書いて、 rename して、消しても、最初の元ファイルの作成日付が残る理由。知らなかった。
https://support.microsoft.com/ja-jp/kb/172190
FsRtlFindInTunnelCache fastfat\create.c を参照。同じディレクトリに、同じファイル名のものを作ったら、キャッシュヒットして、元のファイルの作成日付を拾ってくる。
FltGetTunneledName
Visual Studio 2015 で、新しいプロジェクト、
Visual C++, Windows Driver, Devices, に Filesystem Mini-filter という
テンプレートがある。
cdfs, fastfat を見る限り、オープンのパス検索は、それぞれのファイルシステムが勝手にやって、ディスク上ディレクトリエントリの検索まで、毎回、走る。
linux のような、 dcache で全部終わるケースは無い、ということ。Dissect という関数で、 \ で分解する。ディレクトリはメモリマップされているから、ページがヒットすれば、ディスクには行かないで済む。結局、IRP_MJ_DIRECTORY_CONTROL というのが、アプリのAPI を直接に受ける、上の方の切り口だからしかたない。ntfs は知らない。
と、思ったら、
fat は、親から splay tree で名前 キャッシュを持っているので、メモリ操作だけでオープンできるケースがある。FatFindFcb 参照。
!cbd
windbg 拡張で、引数の irp 相当が見える。
!fltkd Debugger Extension
https://msdn.microsoft.com/en-us/library/windows/hardware/ff540115(v=vs.85).aspx
reparse point filter
https://support.microsoft.com/en-us/kb/319447
新しい文書.docx を開く。
CreateFile read
上書き保存・
CreateFile ~WRD0000.tmp overwriteif read/write
WriteFile
Setinfo Rename 新しい文書.docx => ~WRD0001.tmp
Setinfo Rename ~WRD0000.tmp => 新しい文書.docx
SetDisposition Delete ~WRD0001.tmp
https://technet.microsoft.com/ja-jp/sysinternals/processmonitor を使った。
ついでに、オブジェクトID操作も見ると、
なければ作って、
FSCTL_CREATE_OR_GET_OBJECT_ID 新しい文書.docx
delete ~WRD0000.tmp
get 新しい文書.docx
delete 新しい文書.docx
set ~WRD0000.tmp
という風に引き継ぎます。
fsutil objectid query
コマンドで見える。
Distributed Link Tracking (DLT) Client service and File Replication service (FRS)
に使うそうな。OpenFileById とは別物。
https://technet.microsoft.com/ja-jp/library/cc788098(v=ws.10).aspx
https://www.acc.umu.se/~bosse/
で紹介されているマイクロソフト製でないオープンソースのもの。
HttpDisk
自分で、IoCreateDevice してリードコールバックを提供して、スレッドでTdiBuildSend する。ファイルシステムではなくて、ディスク。socket APIを、TDIにするラッパもある。GPL2以降。
リード対象オフセットと、長さを、 http get range にする。書き込みはできない。主な用途は、 iso イメージをネットワークマウントすること。なるほど。
NFS version 4.1 file system driver for Windows
RxRegisterMinirdr を使う、ミニネットワークリダイレクタ。LGPL3。リダイレクタのサンプルは、今は公開されなくなったので、貴重。
本家
http://www.citi.umich.edu/projects/nfsv4/windows/readme.html
あれ、UNTIL 2013 って書いてあるけど、ミシガン大学の CITI って、終わってしまったの?
例えば、リードのコールバックは、DriverEntry で、
nfs41_ops.MRxLowIOSubmit[LOWIO_OP_READ] = nfs41_Read;
status = RxRegisterMinirdr(&nfs41_dev, drv, &nfs41_ops
こう、登録される。
リード処理は、ユーザ空間のデーモンに投げるだけ。ドライバソースが、7000行で済むのは、全部、こうしているからなのでしょう。
nfs41_driver.c
NTSTATUS nfs41_Read(
IN OUT PRX_CONTEXT RxContext)
{
status = nfs41_UpcallCreate(NFS41_READ, &nfs41_fobx->sec_ctx,
pVNetRootContext->session, nfs41_fobx->nfs41_open_state,
pNetRootContext->nfs41d_version, SrvOpen->pAlreadyPrefixedName, &entry);
バッファはカーネルアドレスだけど、ユーザはどうやって読み書きするの?
entry->u.ReadWrite.MdlAddress = LowIoContext->ParamsFor.ReadWrite.Buffer;
長さとオフセットはこのとおり。ファイルの指定は?
entry->buf_len = LowIoContext->ParamsFor.ReadWrite.ByteCount;
entry->u.ReadWrite.offset = LowIoContext->ParamsFor.ReadWrite.ByteOffset;
status = nfs41_UpcallWaitForReply(entry, io_delay);
リダイレクタのフレームワークから渡されたMDLを、デーモンの空間にマップして、ユーザ仮想アドレスにして、渡す。なるほど。
NTSTATUS marshal_nfs41_rw(
nfs41_updowncall_entry *entry,
unsigned char *buf,
ULONG buf_len,
ULONG *len)
{
status = marshal_nfs41_header(entry, tmp, buf_len, len);
RtlCopyMemory(tmp, &entry->buf_len, sizeof(entry->buf_len));
__try {
entry->buf =
MmMapLockedPagesSpecifyCache(entry->u.ReadWrite.MdlAddress,
UserMode, MmNonCached, NULL, TRUE, NormalPagePriority);
ファイルは、コンテキストで既知なのでしょう。
RX_CONTEXT structure encapsulates an IRP for use by RDBSS
https://msdn.microsoft.com/en-us/library/windows/hardware/ff554751(v=vs.85).aspx
デーモンは、パイプを ioctl で読み書きするだけ。
static unsigned int WINAPI thread_main(void *args)
{
pipe = CreateFile(NFS41_USER_DEVICE_NAME_A, GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING,
0, NULL);
while(1) {
status = DeviceIoControl(pipe, IOCTL_NFS41_READ, NULL, 0,
outbuf, UPCALL_BUF_SIZE, (LPDWORD)&outbuf_len, NULL);
status = upcall_parse(outbuf, (uint32_t)outbuf_len, &upcall);
status = upcall_handle(&upcall);
upcall_marshall(&upcall, inbuf, (uint32_t)inbuf_len, (uint32_t*)&outbuf_len);
status = DeviceIoControl(pipe, IOCTL_NFS41_WRITE,
inbuf, inbuf_len, NULL, 0, (LPDWORD)&outbuf_len, NULL);
}
ユーザ空間でリードを受けるのは、
readwrite.c
static int handle_read(nfs41_upcall *upcall)
{
readwrite_upcall_args *args = &upcall->args.rw;
nfs41_open_stateid_arg(upcall->state_ref, &stateid);
status = read_from_mds(upcall, &stateid);
nfsv4.1 クライアントのリードだから、要するに、メタデータサーバに、オープンファイルハンドルと、ファイル内オフセットと長さを送って、内容をもらう。
}
const nfs41_upcall_op nfs41_op_read = {
parse_rw,
handle_read,
marshall_rw
};
static const nfs41_upcall_op *g_upcall_op_table[] = {
&nfs41_op_read,