2014/04/06

OpenZFS on OS X (7) 拡張属性の互換性

OpenZFS on OS X(O3X)検証の続きということで、拡張属性(Extended Attributes)編です。
特に、ZEVOから利用しているデータセットをO3Xに移行する際の問題についてそれなりに詳細に記しています。

基本的にZEVOについて書いた昔の記事の流れを汲んだものです。


まず、netatalkについて。
O3XではコードベースがZFS on Linux(ZoL)となったので、最新のOpenZFSで作成されたプールもインポートできます。そのため、ZoL環境でnetatalkを動かしているサーバーにトラブルが起こっても、O3Xからインポートして一時的なサルベージができる可能性があります。

netatalkでの拡張属性についてですが、拡張属性に対応したファイルシステムであれば拡張属性に格納し、そうでない場合はAppleDouble(._から始まるファイル)となるようです。しかし、com.apple.ResourceForkだけは格納できないらしくAppleDouble方式になります。本来のZFS(=SolarisのZFS)ではResourceForkも拡張属性に入るようですが、ZoLではそうなっていません(ZoLの制限・Linuxの制限・netatalkの制限・netatalkがSolaris以外のZFSを考慮していないだけ、のどれかは不明)。

ともかく、Linux+ZoL+netatalkのボリュームをMacからマウントすると以下のようになります。
$ ls -la@
total 1024
drwx------@ 1 b00t  staff  264  3 24 21:16 .
 org.netatalk.has-Extended-Attributes   4 
drwxrwxrwt@ 8 root     admin  272  3 24 21:14 ..
 com.apple.FinderInfo  32 
drwxr-xr-x@ 1 b00t  staff  264  3 24 21:15 folderIcon
 com.apple.FinderInfo  32 
drwxr-xr-x@ 1 b00t  staff  264  3 24 21:15 folderLabel
 com.apple.FinderInfo  32 
drwxr-xr-x  1 b00t  staff  264  3 24 21:14 folderNone
-rw-r--r--@ 1 b00t  staff    0  3 24 21:16 textCreater.txt
 com.apple.ResourceFork 1338 
-rw-r--r--@ 1 b00t  staff    0  3 24 21:15 textIcon.txt
 com.apple.FinderInfo  32 
 com.apple.ResourceFork 325188 
-rw-r--r--@ 1 b00t  staff    0  3 24 21:15 textLabel.txt
 com.apple.FinderInfo  32 
 com.apple.metadata:kMDLabel_qygkxhrfarhtxanqhi264amkku  50 
-rw-r--r--  1 b00t  staff    0  3 24 21:15 textNone.txt
ちゃんと拡張属性が保存されています。しかし、このボリュームをサーバー上で見ると以下のようになっています。
# xattr *
folderIcon: user.org.netatalk.Metadata
folderLabel: user.org.netatalk.Metadata
folderNone: user.org.netatalk.Metadata
textCreater.txt: user.org.netatalk.Metadata
textIcon.txt: user.org.netatalk.Metadata
textLabel.txt: user.org.netatalk.Metadata
textLabel.txt: user.com.apple.metadata:kMDLabel_qygkxhrfarhtxanqhi264amkku
textNone.txt: user.org.netatalk.Metadata
どういうことかというと、Mac OS Xでの各種拡張属性がuser.org.netatalkから始まる独自の拡張属性として格納されています。これをnetatalkがMac向けの拡張属性であるように見せているため、O3Xから直接マウントすると当然netatalk独自の拡張属性を理解できないためラベルやアイコンは見えなくなります。

まあ、実際のところサーバーのZFSデータセットをO3Xで読むのはサルベージなどの緊急時くらいだと思うので、netatalk経由で書いた拡張属性が読めないのはさほど問題にならないと思います。
というわけでこの話は蛇足です。

肝心なのがZEVO→O3X移行に際して拡張属性が保存されるかどうかという問題です。
結論から言うと保存されません。が、手動でおおむね何とかできます

ディスク上のデータに変更が無い上に、netatalkと違いどちらもMacから読み書きする以上、何か問題が出るとは正直考えてもいなかったのですが、出ました。

まず、今回はZEVOがインストールされているMac上から「zevo.txt」というファイルを作成し、カスタムアイコン・ラベルを持たせました。これを作成元のMacから見ると以下のようになります。
$ ls -l@
total 0
-rwxrwxrwx@ 1 b00t  wheel  0  3 30 19:57 zevo.txt
 com.apple.FinderInfo 32 
 com.apple.ResourceFork 222790 
 com.apple.metadata:kMDLabel_26x2uentpjgt7lka65qdcazuya 50 
しかし、このファイルが含まれるプールをO3XがインストールされているMac上から見ると、アイコンもラベルも表示されません
ターミナルでの表示が以下のようになっています。
$ ls -l@
total 4
-rwxrwxrwx@ 1 b00t  wheel  0  3 30 19:57 zevo.txt
 com.apple.ResourceFork 222790 
 com.apple.metadata:kMDLabel_26x2uentpjgt7lka65qdcazuya 50 
なんと、com.apple.FinderInfoが消滅しています。ZEVOで再インポートすると問題なく見えるので、正確には見失っています。一方、O3Xから同様に作ったo3x.txtをZEVO側から見ると以下のようになります。
$ ls -l@
total 0
-rwxrwxrwx@ 1 b00t  staff  0  3 30 19:45 o3x.txt
 com.apple.ResourceFork 241097 
 com.apple.metadata:kMDLabel_kqde5prblvaibjifrcn4saxjwi 50 
 com.apple.FinderInfo -1 
今度は、com.apple.FinderInfo -1とサイズが正しく計算できていません(そしてアイコンもラベルも表示されない)。なんにせよ、ZEVOかO3Xで拡張属性、少なくともcom.apple.FinderInfoの扱いに差異がある(もしかするとどちらかが間違えている)ようです。
個人的には、このあたりの挙動を考えるとZEVOがバグってるんじゃ?と思ってしまいます。

そして、このcom.apple.FinderInfoが読めないのが色々とトラブルを引き起こしています。
まず、ラベルについてですが、ラベル付きファイルは各色ごとにcom.apple.metadata:kMDLabelから始まる拡張属性を保持しているのですが、実はFinderでの表示に関係しているのcom.apple.FinderInfo内のラベル情報の方で、こちらの拡張属性の存在意義は謎です(消してもラベルは見えたまま)。謎というのは世間一般的に謎というか個人的に知らないというだけですが・・・
ともかく、com.apple.metadata:kMDLabelが残っていようとcom.apple.FinderInfoが読めない時点でNGです。

また、カスタムアイコンについてはアイコンデータはcom.apple.ResourceForkに格納されているものの、「カスタムアイコンがResourceForkに入っている」という情報自体はcom.apple.FinderInfoに入っているので同様に問題が起こります。

つまり、このcom.apple.FinderInfoをなんとかしないとZEVOからO3X移行時に拡張属性の多くが見かけ上消えてしまうわけです。
というわけで、なんとかしましょう。

とは言っても、32バイトからなるcom.apple.FinderInfoの各バイトがどの情報に対応しているかはわからない(というか調べる気が起きない)ので、O3Xでインポート後に見えるその他の拡張情報から正しいcom.apple.FinderInfoを作って書き込むことは難しいと思います。少なくとも自分には無理です。
なので、方針としてはZEVOで稼働している時点で各ファイルのcom.apple.FinderInfoを全部書き出しておき、O3Xでインポート後に書き戻すという手順になります。

方針としては
ステップ1:xattr -r /Volumes/pool | grep FinderInfo でcom.apple.FinderInfoを持つファイルリストを作る
ステップ2:xattr -x -p com.apple.FinderInfo /Volumes/pool/path/to/file でファイルリスト上のファイルからcom.apple.FinderInfoの16進値を取得・保存
ステップ3:O3Xに移行後、保存した16進値をxattr -x -w com.apple.FinderInfo (hex) /Volumes/pool/path/to/file で書き戻す
という感じです。

そしてできたものが以下のとても汚いスクリプト(xattr.rb)
#!/usr/bin/env ruby
# coding: UTF-8

if `id -un`.strip != "root"
 puts "please use this option with root privilege."
 exit
end

dir = File.expand_path(File.dirname(__FILE__)) + "/work"
`mkdir -p #{dir}`

case ARGV[0]
when "1st"
 if ARGV[1] != nil
  `xattr -r #{ARGV[1]} | grep FinderInfo > #{dir}/finderInfo.txt`
 else
  puts "usage: sudo ./xattr.rb 1st|2nd|3rd [fullPathFor1st]"
  exit
 end
when "2nd"
 files = Array.new
 filesTxt = open("#{dir}/files.txt", "w")
 open("#{dir}/finderInfo.txt").each_line do |line|
  filesTxt.puts "\"#{line.split(": ")[0]}\""
 end
 filesTxt.close

 hexHash = Hash.new { |h, k| h[k] = Array.new }
 path = ""
 hex = ""
 `cat #{dir}/files.txt | xargs xattr -x -p com.apple.FinderInfo`.each_line do |line|
  if line.include?("/")
   hexHash[hex] << path if hex != ""
   path = line[0..-4]
   hex = ""
  else
   hex << line.gsub(/\s/, "")
  end
 end
 hexHash[hex] << path
 `rm #{dir}/files.txt`

 num = 0
 xattrScript = open("#{dir}/xattr.sh", "w")
 hexHash.each do |hex, files|
  tempTxt = open("#{dir}/temp#{num}.txt", "w")
  files.each do |path|
   tempTxt.puts "\"#{path}\""
  end
  tempTxt.close

  xattrScript.puts "cat #{dir}/temp#{num}.txt | xargs xattr -x -w com.apple.FinderInfo #{hex}"
  num += 1
 end
 xattrScript.close
 `chmod +x #{dir}/xattr.sh`
when "3rd"
 `#{dir}/xattr.sh`
else
 puts "usage: sudo ./xattr.rb 1st|2nd|3rd [fullPathFor1st]"
end
sudo ./xattr.rb 1st|2nd|3rd という使い方で、上記ステップに対応した各引数を与えながら都合3回実行することになります。ステップ1では対象データセットのフルパスも渡します。
つまり、ZEVOが動いている状態で
$ sudo ./xattr.rb 1st /Volumes/path/to/pool
$ sudo ./xattr.rb 2nd
と実行し、O3X移行後に
$ sudo ./xattr.rb 3rd
と実行することになります。3段階実行のためにworkディレクトリを作ったりworkの中にcom.apple.FinderInfoの16進値ごとにファイルを生成したりする相当汚いスクリプトになってますが、とりあえず自分の環境では動いたし、無いよりはマシかと・・・

正直かなり適当なスクリプトな上にruby必須なので気まずいのですが、上の情報を見てイチからなんとかするよりはまだ楽かなと。
ZEVOが動いてる=Mountain Lion以下=標準のrubyが1.8=多分1.9入れないと動かない、というあたりも残念ですが。
まあ、それ以前にこのスクリプトを使うにしろ使わないにしろ、拡張属性の読み書きをする時点で多分xattrコマンドをbrewなりで入れるのが必須になると思います。
上の情報を読んでもっとスマートな解決方法があるぞ!と思った方は教えていただけると幸いです。

2 件のコメント:

  1. netatalk側だったらapple_dumpコマンドでFinderInfoの中身もdumpしますよ。

    返信削除
  2. おお、これだとFinderInfoの各バイトが示してる内容もわかりそうですね。

    返信削除