通常、ファイルはそのファイルのパスで識別されます。「c:\Users\User01\Sample.txt」みたいな感じで。同じパスを持つファイルってのは基本的に存在できないので、ファイルパスがユニーク性を持つわけです。
で、この辺でいきなり気づくのは、『ファイルって誰かに移動されちゃうじゃん』ということ。つまり、ファイルが移動されちゃうと、当然ファイルパスが変わるわけで、パスでもってファイルを識別することはできない、ということになります。
何かいい方法ないのかしら、と、いろいろ調べておりますと、Distributed Link Tracking and Object Identifiers というのがありました。レイモンド先生のブログ によると、オブジェクト ID という GUID で識別できるとのこと。さっそくサンプルをビルドしてみます。
#include <windows.h> #include <stdio.h> #include <tchar.h> #include <ole2.h> #include <winioctl.h> int _tmain(int argc, _TCHAR* argv[]) { HANDLE hFile; FILE_OBJECTID_BUFFER buf; DWORD cbOut; GUID guid; WCHAR szGuid[39]; BOOL result; hFile = ::CreateFile(argv[1], 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, 0, NULL); if (hFile == INVALID_HANDLE_VALUE) { _tprintf(_T("CreteFile() Error: 0x%0.8x\n"), ::GetLastError()); goto fin; } result = ::DeviceIoControl(hFile, FSCTL_CREATE_OR_GET_OBJECT_ID, NULL, 0, &buf, sizeof(buf), &cbOut, NULL); if (!result) { _tprintf(_T("DeviceIoControl() Error: 0x%0.8x\n"), ::GetLastError()); goto after_create_file; } ::CopyMemory(&guid, &buf.ObjectId, sizeof(GUID)); ::StringFromGUID2(guid, szGuid, 39); _tprintf(_T("file Object ID is %ws\n"), szGuid); after_create_file: ::CloseHandle(hFile); fin: return 0; }
このオブジェクト ID ってのは、デフォルトでは存在していないので、 DeviceIoControl() 関数 に FSCTL_CREATE_OR_GET_OBJECT_ID を指定して取得します。
逆にオブジェクト ID からファイルへは、OpenFileById() 関数 でファイルハンドルを取得し、 GetFileInformationByHandleEx() 関数 で FILE_NAME_INFO 構造体 を利用して変換可能です。レイモンド先生のブログでは、直接、ファイルハンドルを使ってファイルを開いてるので、ちょっと調べて実装してみました。↓ みたいになります。
#include <windows.h> #include <stdio.h> #include <tchar.h> #include <ole2.h> int _tmain(int argc, _TCHAR* argv[]) { struct FILE_NAME_INFO_AND_BUF { FILE_NAME_INFO fni; TCHAR buf[260]; }; HANDLE hRoot; FILE_ID_DESCRIPTOR desc; HANDLE hFile; FILE_NAME_INFO_AND_BUF fnib = { 0 }; HRESULT hr; TCHAR szRoot[] = _T("C:"); BOOL result; hRoot = ::CreateFile(szRoot, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); if (hRoot == INVALID_HANDLE_VALUE) { _tprintf(_T("CreteFile() Error: 0x%0.8x\n"), ::GetLastError()); goto fin; } desc.dwSize = sizeof(desc); desc.Type = ObjectIdType; hr = ::CLSIDFromString(argv[1], &desc.ObjectId); if (FAILED(hr)) { _tprintf(_T("CLSIDFromString() Error: 0x%0.8x\n"), ::GetLastError()); goto after_create_file_root; } hFile = ::OpenFileById(hRoot, &desc, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, 0); if (hFile == INVALID_HANDLE_VALUE) { _tprintf(_T("OpenFileById() Error: 0x%0.8x\n"), ::GetLastError()); goto after_create_file; } result = ::GetFileInformationByHandleEx(hFile, FileNameInfo, &fnib, sizeof(fnib)); if (!result) { _tprintf(_T("GetFileInformationByHandleEx Error %d\n"), ::GetLastError()); goto after_create_file; } _tprintf(_T("%s\n -> %s%s\n"), argv[1], szRoot, fnib.fni.FileName); after_create_file: ::CloseHandle(hFile); after_create_file_root: ::CloseHandle(hRoot); fin: return 0; }
といったところで、同一ボリュームでのファイル移動は追跡できるようになったんですが、リモートコンピュータ上ではうまく行きません。OpenFileById() 関数の呼び出しで、ERROR_NOT_SUPPORTED が返って来ちゃいます。
本家の Distrbuted Link Tracking、つまり、IShellLink::Resolve() メソッドを利用したショートカットファイルの元ファイル追跡では、リモートコンピュータ上であってもうまく行くんですが...。おそらく、得意の RPC とか使って、クライアントとサーバー間で通信したりしているんでしょうね ( ただし、ドメイン環境では検証してないです。ワークグルーパーなので )。
その ( Distrbuted Link Tracking Client な ) インターフェイスはどうやら公開されていないので、リモートコンピュータを対象にやりたいのであれば、あらかじめショートカットファイルを用意しておいて、IShellLink::Resolve() メソッドを呼び出すか、サーバーアプリを用意しておいて、そっちで各ボリュームに対して OpenFileById() 関数を実行するか、の二択になるのかなと思います。
タイプライブラリから名称 ( helpstring ) を取得してみるサンプル。むかーし、こんなのをやる必要があったときは、tlbinf32.dll を使った気がします。すでにお亡くなりになっちゃってるようですが...。ここ の情報によると、Windows 7 やらでは非サポートだそうで。ま、当たり前ですね。
以下のサンプルでは、『MSCOMCTL.OCX』というファイル名から『Microsoft Windows Common Controls 6.0 (SP6)』って名称を取ってきます。しっかし、これ、『名称』でいいのかな。正式には何て呼べばいいのかわかりません。あれです、参照設定で出てくるやつ。
#include "stdafx.h" #include <Windows.h> #include <OleAuto.h> #define TARGET _T("MSCOMCTL.OCX") int _tmain(int argc, _TCHAR* argv[]) { ITypeLib *pLib; BSTR bstrDocString; HRESULT hr; ::CoInitialize(NULL); hr = ::LoadTypeLib(TARGET, &pLib); hr = pLib->GetDocumentation(-1, NULL, &bstrDocString, NULL, NULL); _tprintf(_T("%s\n -> %s\n"), TARGET, bstrDocString); ::SysFreeString(bstrDocString); pLib->Release(); ::CoUninitialize(); return 0; }
Windows Phone 7.5、いわゆる Mango なスマートフォンが日本でも発売決定だそうで。意外なことに au とは。キャリアを変える気はしないので、スルーな方向です。
という前置きとはまったく無関係に、『プリンタの情報が取得できない ( 正確には、最新の情報が取得できない、というお話 )』という問題でいろいろ調査をしてました。GetPrinter() 関数の動作をソースを追いつつ見てたんですが、その時点では原因がよくわからず。対象がリモートのプリンタなので、ついでにパケットキャプチャもしてみます。
Windows 上でのお話なので、たいていは RPC です。Wireshark でフィルタをかけて、と。アプリ 1 ( ソースあり ) では、確かにプリンタ情報を取りに行っている気配がなく、ということは、ローカルに情報をキャッシュしているんだろうなと。アプリ 2 ( ソースなし ) では、リモートコンピュータに対して『GetPrinter ( レベル 2 )』なパケットが飛んでます。が、ソースがないので、実装がよくわかりません。ここは当然、WinDBG の出番です。
とりあえず、winspool!GetPrinter にブレークポイントをしかけて様子を見ると。アプリ 1、2 とも、あんまり変わらない様子。そもそも、引数にフラグとかありませんし。で、MSDN をいろいろ眺めていると、OpenPrinter2() なんて関数があるんですね。PRINTER_OPTION_NO_CACHE なんてフラグもあるし『これか!』と思ったんですが、今回は Windows XP での動作、ということで、そんな関数は存在しません ( 念のため、非公開関数とかそれっぽい呼び出しがないか、ってのは確認しましたが、とくになさげ )。
それじゃ、もしかして OpenPrinter() 関数あたりかな、ということで、引数の値を esp からちょこちょこ確認してみると、アプリ 1 では指定していない ( NULL ) のに対して、アプリ 2 では、PRINTER_DEFAULTS.DesiredAccess に PRINTER_ALL_ACCESS が指定されているのを確認。どうやらこれみたいです。
アプリ 1 を修正して試してみると、『GetPrinter ( レベル 2 )』なパケットが飛ぶようになりました! こういう直接関係しなさそうなフラグで動作が変わる、ってのは、ちょっと勘弁してほしいと思いつつ、とりあえず原因がわかって納得です。
ちょっと早い感じですが、節電の関係がありまして、一週間ほど夏休みです。といっても、何も準備ができていないので、自宅に引きこもる日々になりそうですが。
微妙にやりたいこともちらほらあるので、これを機会にスタックに積まれたもろもろをポップしておこうかと思ってます。長い休み恒例の大掃除とかもありますし。
最近、仕事をしてる過程において、技術的な部分より精神的な部分がいろいろ気になるようになってきました。たぶん、いい具合に人間として枯れてきたんだと思います。もう少し、そっち方面も整理したいと思いつつ、その辺を整理しすぎるのもよくないのかな、とも思っています。というのは、『あまり突き詰めない方がいいこともある』というのに、今さらながら気づいたからですが、なかなか難しいもんです。
非常にいまさら感があるわけですが、暑い暑い中、基本情報技術者試験を受けてきました。やっぱり、わたしみたいなおっさんはほとんどいなくて、20 代くらいの人ばっかり。いや恥ずかしい。
この手の試験を受けるのは初めてで、ちゃんと解答をメモしてこなかったんですが、おぼろげな記憶と 回答例 を突きあわせると、変なミスさえしてなければ『何とか受かったんじゃないか』ってくらいの感じです。結果が出てみないと何ともかんともですけど。
ま、なんというか、基本的なものだけあって、実務とは全然異なる知識が求められるわけです。それでも試験勉強とかを振り返ってみると、いかに『自分が偏ったことしか知らないのか』ってことに気づかされます。逆に言うと、基本的な知識と実務で必要な知識の乖離、と言えなくもないんでしょうけど。
その辺、うまくスキマを埋められるんであれば、それなりに有効なのかなと ( ここのところ、デバッガを使う関係で微妙にアセンブラを勉強してましたが、思わぬところでその効果が... )。というわけで、受かってれば秋には応用を、ダメだったらも一度再挑戦してみようかと思いました。
よく、『××をするためには "管理者権限" が必要です』なんて言い方を耳にします。そもそも、この "管理者権限" って何なんでしょうか? 先日、ちょっとした問題があって、"管理者権限" についてじっくりと考える機会があったのでした。
で、そのときに至った結論は、『"管理者権限" なんてものはなくて、便宜的に付けられたラベルでしかない』ってところです。ポイントは三つあって、
といった感じ。他にもあるのかも知れませんが、とりあえず、このくらい押さえておけば大丈夫な感じです ( 勝手な判断 )。
最初の『アクセス許可』は、たとえば、『システムフォルダに書き込みをする』とか『HKLM に書き込みをする』とかってやつ。これは、何も管理者権限を持っているから『書き込める』わけではなくて、単に『書き込み』アクセス許可が付与されているかどうか、でしかありません。
つまり、Users グループに『書き込み』アクセス許可を与えてしまえば、何の問題もなく書き込めてしまう、ってことになります。逆に、Administrators グループの『書き込み』アクセス許可を奪ってしまえば、いわゆる『管理者権限』を持ったユーザーでも書き込みはできなくなってしまう、と。
二番目の特権は、『Privilege』ってやつです。こんな感じ でいろいろと定義されてて、有名どころは SE_SYSTEMTIME_NAME みたいにシステム日付を変更するための特権、とか。これも、ローカルセキュリティポリシーの『ユーザー権利の割り当て』で、各ユーザーや各グループに割り当てることができます。
というところで、何となく理解してもらえたと思いますが、『管理者権限』ってのは、単に、『管理者 ( 管理者グループ ) が持つアクセス許可や特権』を総称したものであって、やろうと思えば、一般ユーザーに割り当てることも可能なわけです ( 面倒なんで、そんなことやろう、なんて思う人もそういないでしょうけど )。
この辺、『一般ユーザーで××はできますか?』とかって質問に対しては、『基本的にはできません。無理矢理アクセス許可やら特権を与えればできます』ってことになる可能性がある、って言い換えることもできます。つまり、単純に『できません』とは言えないところに、この問題の難しさがある、って言えるのかも知れません。
おっと、忘れてました。最後の『グループに所属しているか』ってのは、本来的なアクセス許可や特権の検証の手抜きバージョン、ってことになります。だって、本当の意味で言えば、リソースへのアクセス許可を保有しているか、特権を保有しているか、ってところで判断すべきなのに、それをしないわけですから。
ただ、そういう手抜きが行われているのは事実で、IsUserAnAdmin() 関数 やこの関数がラップしている CheckTokenMembership() 関数 では、グループに所属しているかどうか、ってところが検証されます。このほかにも、世の中には『User Name == Administrator』か? なんてハードコードされた検証が行われてたりするので、必ずしも『アクセス許可 & 特権を保有しているか』だけでは対応できなかったりもするんだとは思いますけど。
そういえば、WTSQueryUserToken() 関数 は、『LocalSystem のコンテキストで実行されている & SE_TCB_NAME 特権を保有している』ことが必要条件だったりします ( なので、単なる Administrators グループに所属しているユーザーではうまく動きません )。なんだか、いいんだか悪いんだか、よくわかりません。
ともあれ、『管理者権限が必要』なんて曖昧な言い方は止めてもらって、『どこそこへの△△アクセス許可が必要』とか『○○特権が必要』、あるいは、『□□ユーザー、あるいは、××グループに所属していることが必要』という風に、きちんと明記してもらいたいもんです。