そのファイルには何が記録されているのか?

思考1:テキストファイル

  • MacBookでテキストエディット.appを開いて、半角で「ABC」とだけ入力して、保存した。(ファイル名:abc.txt)
  • 上記で保存したabc.textの情報を見る(command-I)と、「サイズ:4KB(3 バイト)」と確認できた。
  • ハードディスク上は4KBの領域を占有し、その領域の3バイトがファイルの実態であると、これまで理解してきた。
  • 3バイトの中身は、おそらく「ABC」の文字コードだろう。
  • 半角の「ABC」の文字コードASCIIコード表で確認できる。
  • おそらく、16進数で確認すれば「41、42、43」が並んでいるだろう。
  • HexEditor.appで確認してみた。
  • 予想どおりの「41、42、43」が確認できた。めでたし、めでたし?

いや、現実は違った...。Finderから見える景色は3バイトだが、OSレベルではもっと多くの情報が記録されているはずである。(作成日・更新日なども表示されるし)

思考2:JPEGファイル

  • iPhoneで写真を撮った。
  • 今時のカメラは、撮影情報としてGPS座標まで記録される*1
  • そのままweb上に公開すると、撮影場所は丸見えになる。
  • 地図と連動して旅行の写真を公開するなどの特定の目的がなければ、GPS座標は削除したい。

ところで、撮影情報って、どこに記録されているんだろう?

mdlsコマンドが表示する情報

  • Spotlightが検索対象のディレクトリに、撮影情報のある画像:P1010528.JPGをコピーする。
  • mdlsコマンドを実行すると...
$ mdls /Volumes/Macintosh\ Le/Users/zari/Documents/P1010528.JPG 
kMDItemAcquisitionMake         = "OLYMPUS OPTICAL CO.,LTD"
kMDItemAcquisitionModel        = "C2020Z"
kMDItemBitsPerSample           = 32
kMDItemColorSpace              = "RGB"
kMDItemContentCreationDate     = 1999-01-01 00:00:00 +0900
kMDItemContentModificationDate = 1999-01-01 00:00:00 +0900
kMDItemContentType             = "public.jpeg"
kMDItemContentTypeTree         = (
    "public.jpeg",
    "public.image",
    "public.data",
    "public.item",
    "public.content"
)
kMDItemCreator                 = "v954-76"
kMDItemDisplayName             = "P1010528.JPG"
kMDItemEXIFVersion             = "2.1"
kMDItemExposureTimeSeconds     = 0.03333334
kMDItemFinderComment           = "comment"
kMDItemFlashOnOff              = 1
kMDItemFNumber                 = 2
kMDItemFocalLength             = 6.6
kMDItemFSContentChangeDate     = 1999-01-01 00:00:00 +0900
kMDItemFSCreationDate          = 1999-01-01 00:00:00 +0900
kMDItemFSCreatorCode           = ""
kMDItemFSFinderFlags           = 0
kMDItemFSHasCustomIcon         = 0
kMDItemFSInvisible             = 0
kMDItemFSIsExtensionHidden     = 0
kMDItemFSIsStationery          = 0
kMDItemFSLabel                 = 0
kMDItemFSName                  = "P1010528.JPG"
kMDItemFSNodeCount             = 0
kMDItemFSOwnerGroupID          = 20
kMDItemFSOwnerUserID           = 501
kMDItemFSSize                  = 375701
kMDItemFSTypeCode              = ""
kMDItemHasAlphaChannel         = 0
kMDItemISOSpeed                = 100
kMDItemKind                    = "JPEG イメージ"
kMDItemLastUsedDate            = 1999-01-01 00:00:00 +0900
kMDItemOrientation             = 0
kMDItemPixelCount              = 1920000
kMDItemPixelHeight             = 1200
kMDItemPixelWidth              = 1600
kMDItemProfileName             = "sRGB IEC61966-2.1"
kMDItemRedEyeOnOff             = 0
kMDItemResolutionHeightDPI     = 72
kMDItemResolutionWidthDPI      = 72
kMDItemUsedDates               = (
    "1999-01-01 00:00:00 +0900"
)

撮影情報やコメントが表示された。

  • 次に、Spotlightの検索を除外する場所に指定してみる。
  • mdlsコマンドを実行すると...
$ mdls /Volumes/Macintosh\ Le/Users/zari/Documents/P1010528.JPG 
mdls: could not find /Volumes/Macintosh Le/Users/zari/Documents/P1010528.JPG.

mdlsでは何も見つけられないと警告された。

  • 再び、Spotlightの検索対象に戻してみる。
  • 戻して直ぐに、mdlsコマンドを実行すると...
$ mdls /Volumes/Macintosh\ Le/Users/zari/Documents/P1010528.JPG 
kMDItemFSContentChangeDate = 1999-01-01 00:00:00 +0900
kMDItemFSCreationDate      = 1999-01-01 00:00:00 +0900
kMDItemFSCreatorCode       = ""
kMDItemFSFinderFlags       = 0
kMDItemFSHasCustomIcon     = 0
kMDItemFSInvisible         = 0
kMDItemFSIsExtensionHidden = 0
kMDItemFSIsStationery      = 0
kMDItemFSLabel             = 0
kMDItemFSName              = "P1010528.JPG"
kMDItemFSNodeCount         = 0
kMDItemFSOwnerGroupID      = 20
kMDItemFSOwnerUserID       = 501
kMDItemFSSize              = 375701
kMDItemFSTypeCode          = ""

あれあれ、kMDItemFSで始まるファイルの基本情報(サイズ・変更日時・作成日時・ファイル名・ラベル・アクセス権限・タイプ・クリエーターなどの)しか表示されない!

  • それでは、検索インデックスを優先して作成してもらうために /Volumes/Macintosh\ Le/Users/zari/Documents/P1010528.JPG を他のディレクトリに移動して、また戻してみる。
  • mdlsコマンドを実行すると...

今度は、撮影情報やコメントのメタデータが表示された。(Spotlightの検索対象のディレクトリにコピーした、最初の状態)

Spotlightのインデックスの状態

  • Spotlightの検索を除外したボリュームは、インデックスが空っぽになった。
$ ls -a /Volumes/Macintosh\ Le/.Spotlight-V100/Store-V1/Stores
.	..
    • ゆえに、mdlsコマンドで確認しても「何も見つけられない」と警告されたのだ。
  • Spotlightの検索対象であれば、以下のようなインデックスが生成されるのである。
$ ls -a /Volumes/Macintosh\ Le/.Spotlight-V100/Store-V1/Stores/C0514DCD-9DC8-4E5A-9D88-F61BB6688CE6
. 0.indexHead indexState live.0.indexDirectory live.0.indexTermIds .. 0.indexIds journalAttr.1 live.0.indexGroups live.0.indexUpdates .store.db 0.indexPositionTable journalExclusion live.0.indexHead store.db 0.indexArrays 0.indexPositions journalLive live.0.indexIds tmp.SnowLeopard 0.indexCompactDirectory 0.indexPostings journalSync live.0.indexPositionTable 0.indexDirectory 0.indexTermIds live.0.indexArrays live.0.indexPositions 0.indexGroups 0.indexUpdates live.0.indexCompactDirectory live.0.indexPostings
    • Spotlightの検索対象に戻した直後はファイルの基本情報しか表示されなかった。
    • Spotlightのインデックス作成が完了すると、撮影情報やコメントまでちゃんと表示されるようになった。


mdlsコマンドは、ファイルの基本情報と、Spotlightのインデックスから取得できる情報を表示する仕組みのようだ。Spotlightのインデックスについては、各ボリュームのルート直下の /.Spotlight-V100 以下に作成されることが分かった。それでは、ファイルの基本情報はどこに保存されているのだろう?

それを知るには、ファイルシステムHFS+の構造を理解する必要があった。

ファイルの基本情報とは?

  • 書籍に本文と目次があるように、ハードディスクにもデータ(本文)とインデックス(目次)がある。
  • 広大な記憶領域に点在するデータに素早くアクセスできるのは、データの位置を記録しているインデックスのおかげである。
  • いかに素早く・正確に・確実に・無駄なく情報を保存・検索できるか、工夫を凝らして、データとインデックスの形式を規定したものが、ファイルシステムだ。
  • インデックスにはデータの位置が記録されているのはもちろん、サイズ・変更日時・作成日時・ファイル名・ラベル・アクセス権限・タイプ・クリエーターなどの基本情報も含まれている。
  • それらの基本情報が手がかりとなって、目的のデータに素早くアクセスしたり、効率的に管理できるのだ。

つまり、ファイルの基本情報はインデックスにある。

HFS+の構造

上記ページと現状の自分の知識で、果たしてどれだけ正確に理解できているか怪しいが、自分の理解を書いてみた。

セクタ
  • 無地のハードディスクは一般的にセクタと呼ばれる単位(512バイト、ドライバが読み書きする単位)で区切られている。


OSX 10.6環境のHFS+で、HDを初期化すると...

アロケーションブロック
  • 8つのセクタをまとめたアロケーションブロック単位(4096バイト=4KB)でデータにアクセスする仕様になる。(保存する時の最小単位)
  • アロケーションブロックはユニークな(重複しない)番号(最大2の32乗 = 4,294,967,296)によって識別される。
ボリュームヘッダー
  • 先頭から1024バイトの位置に、ボリュームヘッダーが追加される。
  • そこにはボリュームに関する様々な重要な情報が記載されている。そして、以下の特殊な管理ファイルが存在する場所も明記されている。
    • カタログファイルの位置・大きさ
    • エクステントオーバーフローファイルの位置・大きさ
    • アトリビュートファイルの位置・大きさ
    • アロケーションファイル(ビットマップ)の位置・大きさ
    • スタートアップファイルの位置・大きさ
フォーク
  • HFS+は、フォークと呼ばれる単位で分類して、ファイルを保存する。
  • 通常は、データフォークに保存される。(カタログファイルが管理)
  • そして、旧Mac OS特有のリソースフォークも扱える。(カタログファイルが管理)
  • さらに、独自のフォークを定義(拡張属性 = EAと呼ばれる)して、自由に追加できる。(アトリビュートファイルが管理)*2
エクステント
カタログファイルとエクステントオーバーフローファイル
  • カタログファイルでは、1つのフォークに対して、8つのエクステントまで記録できる。
  • ファイルを保存する時に、連続した領域を確保できれば、1つのエクステントだけで足りてしまう。(可能な限り1つのエクステントになることを目指す仕様)
  • 連続した領域を確保できない場合*3でも、8つの領域までなら、カタログファイルで直接管理される。
  • 9つ以上の領域に分けて保存する場合は、エクステントの続きは、エクステントオーバーフローファイルに引き継がれる。


このように、カタログファイル・エクステントオーバーフローファイル・アトリビュートファイルが、一般的なファイルのインデックス情報となっている。

  • カタログファイルは以下のように定義されたレコードの集合である。(B-tree、バランスツリーと呼ばれる構造になっている)
  • データフォークの格納場所(dataFork)以外にも、様々な属性が付属しているのが分かる。これがファイルの基本情報となっている。
struct HFSPlusCatalogFile {
    SInt16              recordType;
    UInt16              flags;
    UInt32              reserved1;
    HFSCatalogNodeID    fileID;
    UInt32              createDate;
    UInt32              contentModDate;
    UInt32              attributeModDate;
    UInt32              accessDate;
    UInt32              backupDate;
    HFSPlusBSDInfo      permissions;
    FileInfo            userInfo;
    ExtendedFileInfo    finderInfo;
    UInt32              textEncoding;
    UInt32              reserved2;
 
    HFSPlusForkData     dataFork;
    HFSPlusForkData     resourceFork;
};
typedef struct HFSPlusCatalogFile HFSPlusCatalogFile;


struct HFSPlusForkData {
    UInt64                  logicalSize;
    UInt32                  clumpSize;
    UInt32                  totalBlocks;
    HFSPlusExtentRecord     extents;    /* HFSPlusExtentDescriptorの配列[8] */
};
typedef struct HFSPlusForkData HFSPlusForkData;
 
typedef HFSPlusExtentDescriptor HFSPlusExtentRecord[8];


struct HFSPlusExtentDescriptor {
    UInt32                  startBlock; /* 先頭のアロケーションブロックの番号 */
    UInt32                  blockCount; /* 連続するアロケーションブロックの数 */
};
typedef struct HFSPlusExtentDescriptor HFSPlusExtentDescriptor;
特殊な管理ファイル
  • ちなみに、カタログファイル自身もファイルであるから、上記と同じ要領で管理されている。
    • ボリュームヘッダー内のカタログファイルの位置・大きさは、8つのエクステントで管理される。
    • 9つ以上の領域に分けて保存する場合は、エクステントオーバーフローファイルに引き継がれる。
    • ただし、フォークは1つだけ。(データ・リソース・拡張属性フォークの区別なし)
  • ボリュームヘッダー内で管理される、その他の(エクステント-バーフロー・アトリビュートアロケーション・スタートアップ)ファイルも同じ仕組みである。

拡張属性(EA)を確認する

  • mdlsコマンドで表示されるkMDItemFSで始まる属性情報は、カタログファイルの情報が元になっているようだ。
  • 一方、ファイルの基本情報以外の属性は、アトリビュートファイルで関連付けられる。
  • その属性はSpotlightによって収集され、Spotlightのインデックスに情報が集まる。
  • mdlsコマンドはSpotlightのインデックスを見て、基本情報以外の属性も表示してくれるようになるのだ。
  • ところで、mdlsコマンドが表示するのはSpotlightのインデックスの状態であり、拡張属性(EA)そのものではない。
    • Spotlightがすべての拡張属性(EA)を収集するという保証もない。(Spotlightのインデックスに追加されない属性だってあるかも)
    • また、拡張属性(EA)以外の情報も収集している可能性もある。(テキストの内容検索も出来ることだし)
  • 拡張属性(EA)の本体は、データフォークと同じように、それぞれのフォークに記録*4されている。
  • 拡張属性(EA)は、Spotlightの検索対象である・ないに関係なく、恒久的に保存される。
  • 拡張属性(EA)は、xattrコマンドで確認できる。やってみた。
$ xattr -l ~/Desktop/abc.txt
com.apple.TextEncoding: utf-8;134217984
  • com.apple.TextEncodingという属性に、テキストエンコーディングが保存されていた!
  • そういえば、保存する時にテキストエンコーディングを指定する項目があった。それが拡張属性(EA)に保存されていたのだ。

  • ちなみに、mdlsコマンドも試してみた。
$ mdls ~/Desktop/abc.txt
kMDItemContentCreationDate     = 2010-01-29 16:35:19 +0900
kMDItemContentModificationDate = 2010-01-29 16:35:19 +0900
kMDItemContentType             = "public.plain-text"
kMDItemContentTypeTree         = (
    "public.plain-text",
    "public.text",
    "public.data",
    "public.item",
    "public.content"
)
kMDItemDisplayName             = "abc.txt"
kMDItemFSContentChangeDate     = 2010-01-29 16:35:19 +0900
kMDItemFSCreationDate          = 2010-01-29 16:35:19 +0900
kMDItemFSCreatorCode           = ""
kMDItemFSFinderFlags           = 0
kMDItemFSHasCustomIcon         = 0
kMDItemFSInvisible             = 0
kMDItemFSIsExtensionHidden     = 0
kMDItemFSIsStationery          = 0
kMDItemFSLabel                 = 0
kMDItemFSName                  = "abc.txt"
kMDItemFSNodeCount             = 0
kMDItemFSOwnerGroupID          = 20
kMDItemFSOwnerUserID           = 501
kMDItemFSSize                  = 3
kMDItemFSTypeCode              = ""
kMDItemKind                    = "Text Document"
kMDItemLastUsedDate            = 2010-01-29 16:35:20 +0900
kMDItemUsedDates               = (
    "2010-01-29 00:00:00 +0900"
)
  • どうやらSpotlightのインデックスには、テキストエンコーディングの情報は収集されないようだ。
  • 試しに、abc.txtのSpotlightコメントに「CommentTest」と書き込んでみると...
$ xattr -l ~/Desktop/abc.txt 
com.apple.TextEncoding: utf-8;134217984
com.apple.metadata:kMDItemFinderComment:
00000000  62 70 6C 69 73 74 30 30 5B 43 6F 6D 6D 65 6E 74  |bplist00[Comment|
00000010  54 65 73 74 08 00 00 00 00 00 00 01 01 00 00 00  |Test............|
00000020  00 00 00 00 01 00 00 00 00 00 00 00 00 00 00 00  |................|
00000030  00 00 00 00 14                                   |.....|
00000035
  • Spotlightコメントは、com.apple.metadata:kMDItemFinderCommentという属性で保存されるようだ。


これでやっと、思考1:テキストファイルの疑問が解決した!

撮影情報はどこ?

  • 次に、撮影情報付きのJPEGファイルの拡張属性(EA)を確認してみた。
$ xattr -l /Volumes/Macintosh\ Le/Users/zari/Documents/P1010528.JPG 

あれあれ?何も表示されない。予想では、様々な情報が表示されることを期待したのだが...。

Exif

  • 調べてみると、撮影情報はExifという規格で、画像ファイルの中に埋め込まれていた。
  • つまり、データフォークなのだけど、ルールを決めて撮影情報を区別できるようにしてあるのだ。

つまり、データフォークの中に、HFS+のようなインデックスとフォークの構造を作って、そこに属性情報を埋め込んであるのだ。

検証
  • jpegファイルの16進数ダンプ。


  • どんな書式か、検証してみた。

ff d8
開始マーカー
ff e1
Exif形式の情報 APP1であることを示す
1c 45
APP1のサイズは0x1c45バイト
45 78 69 66 00 00
Exifヘッダー
49 49 2a 00 08 00 00 00
TIFFヘッダー TIFFヘッダー TIFFヘッダー
Intel形式 Tagマーカー オフセット値=8
0b 00
エントリ数=11個
タグ タイプ カウント 値、またはそのオフセット値
0e 01 02 00 20 00 00 00 92 00 00 00
画像の説明 ASCII文字 32文字 0x92バイト先の「OLYMPUS DIGITAL CAMERA」
0f 01 02 00 18 00 00 00 b2 00 00 00
メーカー名 ASCII文字 24文字 0xb2バイト先の「OLYMPUS OPTICAL CO.,LTD」
10 01 02 00 07 00 00 00 ca 00 00 00
機種名 ASCII文字 7文字 0xcaバイト先の「C2020Z」
... ... ... ...
      • オフセット値は、TIFFヘッダー先頭からの位置

タグを調べながら、目で追って行くのはとっても大変...。

exiftoolコマンド
  • 実は、そんなに苦労しなくても、exiftoolコマンドが公開されていた。(素晴らしい!)
$ exiftool /Volumes/Macintosh\ Le/Users/zari/Documents/P1010528.JPG
ExifTool Version Number         : 8.08
File Name                       : P1010528.JPG
Directory                       : /Users/zari/Desktop
File Size                       : 367 kB
File Modification Date/Time     : 1999:01:01 00:00:00+09:00
File Permissions                : rwxrwxrwx
File Type                       : JPEG
MIME Type                       : image/jpeg
Exif Byte Order                 : Little-endian (Intel, II)
Image Description               : OLYMPUS DIGITAL CAMERA
Make                            : OLYMPUS OPTICAL CO.,LTD
Camera Model Name               : C2020Z
Orientation                     : Horizontal (normal)
X Resolution                    : 72
Y Resolution                    : 72
Resolution Unit                 : inches
Software                        : v954-76
Modify Date                     : 0000:00:00 00:00:00
Y Cb Cr Positioning             : Co-sited
Exposure Time                   : 1/30
F Number                        : 2.0
Exposure Program                : Program AE
ISO                             : 100
Exif Version                    : 0210
Date/Time Original              : 0000:00:00 00:00:00
Create Date                     : 0000:00:00 00:00:00
Components Configuration        : Y, Cb, Cr, -
Compressed Bits Per Pixel       : 2
Exposure Compensation           : 0
Max Aperture Value              : 2.0
Metering Mode                   : Multi-segment
Light Source                    : Unknown
Flash                           : Fired
Focal Length                    : 6.6 mm
Special Mode                    : Normal, Sequence: 0, Panorama: (none)
Quality                         : HQ (Normal)
Macro                           : Off
Black And White Mode            : Off
Digital Zoom                    : 0.0
Focal Plane Diagonal            : 7.8 mm
Lens Distortion Params          : -291 -531 -574 -265 -460 -479
Resolution                      : 2
Camera Type                     : SR954
Camera ID                       : OLYMPUS DIGITAL CAMERA
Data Dump                       : (Binary data 122 bytes, use -b option to extract)
User Comment                    : 
Flashpix Version                : 0100
Color Space                     : sRGB
Exif Image Width                : 1600
Exif Image Height               : 1200
Interoperability Index          : R98 - DCF basic file (sRGB)
Interoperability Version        : 0100
File Source                     : Digital Camera
Scene Type                      : Directly photographed
Compression                     : JPEG (old-style)
Thumbnail Offset                : 2048
Thumbnail Length                : 4875
Image Width                     : 1600
Image Height                    : 1200
Encoding Process                : Baseline DCT, Huffman coding
Bits Per Sample                 : 8
Color Components                : 3
Y Cb Cr Sub Sampling            : YCbCr4:2:2 (2 1)
Aperture                        : 2.0
Image Size                      : 1600x1200
Scale Factor To 35 mm Equivalent: 5.5
Shutter Speed                   : 1/30
Thumbnail Image                 : (Binary data 4875 bytes, use -b option to extract)
Circle Of Confusion             : 0.005 mm
Field Of View                   : 52.4 deg
Focal Length                    : 6.6 mm (35 mm equivalent: 36.6 mm)
Hyperfocal Distance             : 4.02 m
Light Value                     : 6.9

かなりの情報量だ!Spotlightは、これらの情報を読み取って、検索インデックスに取り込んでいたのだ。(それがmdlsコマンドで出力された)

exifの削除

  • OSX 10.6環境で利用できるGUIExif情報削除ツールは見つからなかったが...
  • exiftoolコマンドの以下のオプション指定さえ覚えておけば、便利に使えそう。
# IMG_0439.JPG の GPS情報を削除する
$ exiftool -GPSLatitude= -GPSLongitude= -GPSAltitude= -overwrite_original ~/Desktop/IMG_0439.JPG
# ...あるいは
$ exiftool -geotag= -overwrite_original ~/Desktop/IMG_0439.JPG
    1 image files updated

# P1010528.JPGから、すべてのExif情報を削除する
$ exiftool -all= -overwrite_original ~/Desktop/IMG_0439.JPG 
    1 image files updated

# デスクトップの.jpg画像から、すべてのExif情報を削除する
$ exiftool -all= -overwrite_original ~/Desktop/*.JPG 
    2 image files updated

# デスクトップのファイルから、すべてのExif情報を削除する(pdfも対象になった)
$ exiftool -all= -overwrite_original ~/Desktop 
    1 directories scanned
    4 image files updated

# ちなみに、すべてのExif情報を削除しても、以下の内容だけは確認できた
$ exiftool ~/Desktop/IMG_0439.JPG 
ExifTool Version Number         : 8.08
File Name                       : IMG_0439.JPG
Directory                       : /Users/zari/Desktop
File Size                       : 1585 kB
File Modification Date/Time     : 2010:01:31 14:03:54+09:00
File Permissions                : rw-r--r--
File Type                       : JPEG
MIME Type                       : image/jpeg
Image Width                     : 2048
Image Height                    : 1536
Encoding Process                : Baseline DCT, Huffman coding
Bits Per Sample                 : 8
Color Components                : 3
Y Cb Cr Sub Sampling            : YCbCr4:2:0 (2 2)
Image Size                      : 2048x1536
      • exiftool -TagName='abc' で、TagNameに'abc'が書き込まれる。
      • exiftool ~/Desktop/IMG_0439.JPGでの出力が正式なタグ名称とは限らない。(例:Camera Model Name...NG → Model...OK)
      • exiftool -TagName= のみだと、何も値を指定しないので削除されることになる。(exiftool -TagName='' でも同じ結果になった)
      • exiftool -overwrite_originalオプションによって、元ファイルに上書きされる。-overwrite_original無しなら、オリジナルがバックアップされる。


以上でやっと、思考2:JPEGファイルで悩んだ撮影情報の場所も分かり、無事削除できたのであった。

  • ちなみに、png画像は一般的にはExif情報を保持していない。
  • webにアップロードする際に、サイズ変更も兼ねてpng形式に変換すると、Exif情報は削除された。(MacBookのプレビュー.appで変換した場合)

*1:設定 >> 一般 >> 位置情報サービス をOFFにすれば記録されない

*2:過去にカタログファイルが管理していたリソースフォークも、最近はEAで com.apple.ResourceFork として扱うようになった。

*3:ファイルの追加・削除を繰り返していると、連続した領域を確保できなくなり、データの並びが飛び飛びな状態になるかもしれないのだ。

*4:そのフォークの位置をアトリビュートファイルが教えてくれる。