ShadowHashと同じく、dsLocalのアカウント情報も/var/db以下に格納される。これはその名の通り/var/db/dslocalというディレクトリになる(図16)。

図16: dsLocalのアカウント情報は、/var/db/dslocalというディレクトリに格納される

このdslocalディレクトリの下にはdsmappings、indices、nodesという3つのディレクトリが存在する(実行例8)。nodesから順番に見てみよう。

実行例8: dsLocalのアカウント情報は、/var/db/dslocalというディレクトリに格納される

 # cd /var/db/dslocal
 # ls -l
 total 0
 drwxr-xr-x  4 root  wheel  136 10 12  2007 dsmappings
 drwxr-xr-x  3 root  wheel  102 12  9  2007 indices
 drwxr-xr-x  3 root  wheel  102 10 12  2007 nodes

nodesでは、実際の情報がplist形式で格納されている。/var/db/nodes/Defaultsディレクトリ配下には、aliasesやusers、machinesといったディレクトリが存在する。これらは、かつて/etc以下にファイルとして置かれていたデータと同等のものだ(実行例9)。

実行例9: /var/db/nodes/Defaultsディレクトリ配下には、aliasesやusers、machinesといったディレクトリが存在する

# cd /var/db/dslocal
# ls -l
total 0
drwxr-xr-x  4 root  wheel  136 10 12  2007 dsmappings
drwxr-xr-x  3 root  wheel  102 12  9  2007 indices
drwxr-xr-x  3 root  wheel  102 10 12  2007 nodes
# cd nodes
# ls
Default
# cd Default/
# ls
aliases     config      groups      machines    networks    users
# cd users/
# ls
_amavisd.plist      _devdocs.plist      _pcastagent.plist   _svn.plist      _xgridcontroller.plist
_appowner.plist     _eppc.plist     _pcastserver.plist  _teamsserver.plist  agih.plist
_appserver.plist    _installer.plist    _postfix.plist      _tokend.plist       daemon.plist
_ard.plist      _jabber.plist       _qtss.plist     _unknown.plist      ldap.plist
_atsserver.plist    _lp.plist       _sandbox.plist      _update_sharing.plist   nobody.plist
_calendar.plist     _mailman.plist      _securityagent.plist    _uucp.plist     root.plist
_clamav.plist       _mcxalr.plist       _serialnumberd.plist    _windowserver.plist test.plist
_cvs.plist      _mdnsresponder.plist    _spotlight.plist    _www.plist
_cyrus.plist        _mysql.plist        _sshd.plist     _xgridagent.plist

dsclで見えていた情報は、実際にはこうしたディレクトリであり、実際のレコードはplist形式のファイルとして格納されている(リスト1)。

リスト1 実際のレコードはplist形式のファイルとして格納されている

 <?xml version="1.0" encoding="UTF-8"?>
 <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
 <plist version="1.0">
 <dict>
      <key>_writers_LinkedIdentity</key>
      <array>
           <string>me</string>
      </array>
      <key>_writers_hint</key>
      <array>
           <string>me</string>
      </array>
      <key>_writers_jpegphoto</key>
      <array>
           <string>me</string>
      </array>
      <key>_writers_picture</key>
      <array>
           <string>me</string>
      </array>
      <key>authentication_authority</key>
      <array>
       <string>;Kerberosv5;;me@LKDC:SHA1.5E52FEC5EB1FB35489AC7027FFD8439124DFF9A9;LKDC:SHA1.5E52FEC5EB1FB35489AC7027FFD8439124DFF9A9;</string>
           <string>;ShadowHash;HASHLIST:&lt;SALTED-SHA1,SMB-NT&gt;</string>
      </array>
      <key>generateduid</key>
      <array>
           <string>DBAB71C1-3475-4A67-8FDF-1B8221FC9E04</string>
      </array>
      <key>gid</key>
      <array>
           <string>501</string>
      </array>
      <key>home</key>
      <array>
           <string>/Users/me</string>
      </array>
      <key>jpegphoto</key>
      <array>
           <data>
           /9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAICAgICAQICAgICAgIDAwYEAwMD
       AwcFBQQGCAcICAgHCAgJCg0LCQkMCggICw8LDA0ODg4OCQsQEQ8OEQ0ODg7/
       ……
       ……
       ……
       jbcArDJDFSdwJIPJwGIPOtLLpWhpCPEPgfRlR5Jkg0PSHumWRlH7wMVQIMk8
           YIGVznBymnS6DPcRXEc+vanqs0hmhudWLrbryMExhn3rtx8pwRwSvXFIR//Z
           </data>
      </array>
      <key>name</key>
      <array>
           <string>me</string>
      </array>
      <key>passwd</key>
      <array>
           <string>********</string>
      </array>
      <key>picture</key>
      <array>
           <string>/Library/User Pictures/Animals/Dragonfly.tif</string>
      </array>
      <key>realname</key>
      <array>
           <string>Machine Owner</string>
      </array>
      <key>shell</key>
      <array>
           <string>/bin/bash</string>
      </array>
      <key>uid</key>
      <array>
           <string>501</string>
      </array>
 </dict>
 </plist>

XMLテキストであるplistでは検索性に劣る。このためデータそのものであるplist以外に検索用のインデックスとなる情報が、先の3つのディレクトリのうちの1つであるindicesディレクトリには格納されている。情報の検索についてはまずこのindecesディレクトリのインデックスを使って行い、それからnodesの下の実際のデータにアクセスするという手順になっている模様だ。

indecesディレクトリには、nodeと同じくDefaultディレクトリがあり、その下にindexというファイルが1つだけ置かれている(実行例10)。

実行例10: indecesディレクトリ配下のDefaultディレクトリには、indexというファイルが1つだけ置かれている

 # cd /var/db/dslocal/indices/
 # ls
 Default
 # cd Default/
 # ls -l
 total 120
 -rw-r--r--  1 root  wheel  59392  7 23 20:05 index

このindexというファイルはsqlite3のデータベースであり、sqlite3コマンドを利用することで読み取りが可能だ(実行例11)。

実行例11: indexというファイルはsqlite3のデータベースで、sqlite3コマンドを利用し読み取り可能

 # cd /var/db/dslocal/indices/
 # cd Default/
 # sqlite3 index
 SQLite version 3.5.8
 Enter ".help" for instructions
 sqlite> select * from "dsAttrTypeStandard:RecordName" where value = "me" ;
 me.plist|users|me
 me.plist|groups|me

sqlite3コマンドでindexファイルを読み込み、.tablesコマンドでテーブル一覧をとると、先のnodesディレクトリにあったのと同じusers、groups、computersといったテーブルや、dsAttrTypeStandard:GeneratedUIDなどといったplist内部の属性情報ごとのテーブルがある(表2)。

表2については、スキーマ、データ例も含めたバージョンを「list2_dslocalschema.pdf」として用意しています

表2: plist内部の属性情報テーブル

名前 役割
users 各ユーザのplistファイルとその更新時間を記録
groups 各グループのplistファイルとその更新時間を記録
computers 各コンピュータのplistファイルとその更新時間を記録
dsAttrTypeStandard:RealName レコードの形式(ユーザ、グループ、コンピュータ)と、そのReal name (フルネームなど長い名称)から、対応するplistファイル名を取得するためのテーブル
dsAttrTypeStandard:GeneratedUID レコードの形式(ユーザ、グループ、コンピュータ)と、各情報に一意に対応するUUIDから、対応するplistファイル名を取得するためのテーブル
dsAttrTypeStandard:RecordName ユーザやグループのレコードの形式および名称(アカウント名など短い名称)から対応するplistファイル名を取得するためのテーブル
dsAttrTypeStandard:GroupMembers グループに所属するユーザのGeneratedUID(UUID)から対応するグループのplistファイル名を取得するためのテーブル
dsAttrTypeStandard:UniqueID UIDから対応するユーザのplistファイルを取得するためのテーブル
dsAttrTypeStandard:Member アカウント名から所属するグループのplistファイル名を取得するためのテーブル
dsAttrTypeStandard:PrimaryGroupID GIDから対応するグループのplistファイル名を取得するためのテーブル

例えば、dsAttrTypeStandard:GeneratedUIDではplistのファイル名と、userあるいはgroupなどといったレコードのタイプ、そしてUUIDが記録されている。UUIDにはインデックスが張られており、高速に検索が可能だ。これにより、nodesディレクトリ以下のplistを見て回らなくても、特定の情報から高速に対応するplistファイルや頻繁に使われる情報を取り出せる。

3つのディレクトリのうちの最後の1つ、dsmappingディレクトリにはAttributeMappings.plistとRecordMappings.plistという2つのplistが存在し、Open Directoryで正規化された設定名(例: dsAttrTypeStandard:RealName)と、plistでの短く分かりやすい名称(例: realname)との関連付けの情報が格納されている(図17)。

図17: dsmappingsの例: これはRecordMappingsの例で、Open Directoryの各レコードを格納したフォルダがdsLocalのどのフォルダに対応するかを記述している。同様にAttributeMappingsがあり、こちらはOpenDirectoryの各属性名が、plistの中のどのキーに対応するか関連付ける

dsclのようなDirectoryService APIを用いるアプリケーションからdsAttrTypeStandard:RealNameという名前で参照された情報は、このテーブルで読み替えられ、実際にはrealnameという名称で格納されている情報がアクセスされる。

なお、こうした関連付けはdsLocalに限らず、LDAPのディレクトリサービスを利用する場合でも用いられている。そもそもユーザ情報を登録するディレクトリのノードの名称や、それぞれの属性の名称といったキーワードはディレクトリサービスごとでまちまちだ。Open Directoryではあえて正規化された名称を用意し、ディレクトリサービスごとにマッピングを行い関連付けることで、どのディレクトリでもDirectoryService APIでは同じ正規化された名前で参照できるように工夫されている。

なお、今回は仕組みを説明するためにあえてファイルそのものへの直接アクセスを行ったが、dsLocalへのアクセスはもちろんこのような無理矢理な方法で行うべきではない。ましてやシステムの稼働中にdsLocalやShadowHashの情報の編集を行ってしまうと、まず間違いなくメモリ上にキャッシュされた情報と齟齬を起こし、最悪Mac OS Xそのものの稼働に支障を来してしまう。こうした情報へのアクセスはdsclなどのツールを利用する、あるいはOpen DirectoryのAPIであるDirectoryService APIを利用して行うべきである。

推奨されるのはdsclやシステム環境設定のアカウントパネル、Mac OS X Serverならば一連のサーバ管理ツールなどの、Appleがすでに提供しているツールを利用することだ。

他に選択肢がある限りDirectoryService APIを利用すべきではない。残念ながら、DirectoryService APIは非常に出来が悪く、生のまま使うのは耐え難い代物であるからだ。

『Mac OS X独自のディレクトリサービス「Open Directory」とdsLocal(後編) - the inner universe of Leopard』ではDirectoryService APIを使ってみるが、あくまで参考にとどめてほしい。