Go言語でzipファイルを作る、ただしWindowsに限る
Go言語でzipファイルを扱うとき、とりあえず"archive/zip"を使いますよね。
しかし、使ってみて、気付いたことがある。
思っていたzipファイルを作ることができなかった。
ちなみに今は古いgo 1.6.2を使ってます。最新は1.8です。
zipのエントリ(zip.FileHeader)を作る方法が複数あるみたいだ。
- ファイルパスから作る writer.Create(relativepath)
- FileInfoから作る zip.FileInfoHeader(fileInfo)
- 自分で作る &zip.FileHeader{}
ファイルパスから作ると細かいことができない。
FileInfoから作ると、unix形式(rwxrwxrxw)が強制される <- SetModeを呼ばせたくない。
仕方ないから自分で作るよ。
属性
一般的なファイルは"A"が7zで表示されます。エクスプローラーの表示を変えれば属性も出せます。
setModeを呼び出すとホストOSがUnixになり、属性もrwx形式になる。
そのため、それは迂回して、自分でExternalAttrsを設定しないといけない。
とりあえず、何を設定すればどうなるのか手で打ち込んでチェックしてみた。
D ディレクトリ <- fh.ExternalAttrs = 16 R 読み取り専用 <- fh.ExternalAttrs = 1 H 隠しファイル <- fh.ExternalAttrs = 2 A アーカイブ <- fh.ExternalAttrs = 32 S システム ファイル <- fh.ExternalAttrs = 4 I 非インデックス対象ファイル <- 不明。。。力尽きた。 L 再解析ポイント <- fh.ExternalAttrs = 1024 - その属性以外
上記の調査は、だいたいあってると思う。
src/syscall/ztypes_windows.go - The Go Programming Language
https://msdn.microsoft.com/ja-jp/library/windows/desktop/gg258117(v=vs.85).aspx
あ、これでいいのかも。
fh.ExternalAttrs = data.FileAttributes
すみません、dataっつーのは、更新日のところで説明しますが、
fileInfo.Sys().(*syscall.Win32FileAttributeData).FileAttributesということです。
ファイル所有者
java.nio.file.attributeならファイル所有者の情報を取得できるらしい。
goの場合、データをどこから抜いてくるのか発見できてない。
できるんだろうか??
そして、どうやって詰めるんだっけ・・・。
どうも、zipにするときには詰められないように思える。
7zには所有者情報は見えないし。
とりあえず、zipのためには取得する必要はなさそうだ。
おそらく、これを実現するには、fileの情報をもとに、セキュリティ情報を拾って、
その情報をもとにルックアップするイメージなんだろう。(C++でもJavaでもそんなイメージ)
更新日時、作成日時、アクセス日時
更新日時のみであれば、FileHeader#SetModTime(info.ModTime())で十分です。
作成日時とアクセス日時は、FileHeaderのExtraを設定する必要がある。
作成日時とアクセス日時は、FileInfoのSys()から取得する。
とりあえず以下の記事は参考になるでしょう。
data := info.Sys().(*syscall.Win32FileAttributeData)
これを使ってExtraに突っ込む。
注意事項はリトルエンディアンであるということだ。
どういう[]byteになればいいのかは適宜バイナリエディタでzipファイルを比較確認するほうがいい。
理屈的な形式は、ここが参考になったので是非ご覧いただきたい。
https://opensource.apple.com/source/zip/zip-6/unzip/unzip/proginfo/extra.fld
リトルエンディアンを簡単に扱うためには、
"encoding/binary"パッケージのLittleEndianを使うほうが間違いがないが、
bufの先頭から詰めてくるので、Extraのbyte配列に直接詰め込めない。
binary.LittleEndian.PutUint32(buf, data)
とりあえず自分はこんな感じで書いた。間違ってたらスマン。
序盤は自前でエンディアンを意識して書いたが、後半はbinaryパッケージを利用する関数を用意した。
data := info.Sys().(*syscall.Win32FileAttributeData) var extra []byte extra = append(extra, 0x0a, 0x00) // NTFS @Short extra = append(extra, 0x20, 0x00) // TSize @Short extra = append(extra, 0x00, 0x00, 0x00, 0x00) // Reserved @Long extra = append(extra, 0x01, 0x00) // NTFS attribute Tag @Short extra = append(extra, 0x18, 0x00) // Size of attribute#1 @Short extra = appendUint32(extra, data.LastWriteTime.LowDateTime) extra = appendUint32(extra, data.LastWriteTime.HighDateTime) extra = appendUint32(extra, data.LastAccessTime.LowDateTime) extra = appendUint32(extra, data.LastAccessTime.HighDateTime) extra = appendUint32(extra, data.CreationTime.LowDateTime) extra = appendUint32(extra, data.CreationTime.HighDateTime)
あ、すみませんが、infoってのはFileInfoですよ。
filepath.Walkを呼ぶので、WalkFuncを実装していて、引数でFileInfoが渡ってきます。
ホストOS
7zで言うところのホストOSは、FileHeaderのCreatorVersionに相当する。
fh.CreatorVersion = 11 << 8
意味不明な数字がでてくるでしょう。
goのstructを見ると数字が解るので、それを8ビットシフトして使えばなんとかなる。
この11はNTFS。