Logo address

Mac Life

2021-09-17
2021-11-22 追加
2021-11-28 追加

はじめに

我が家の(と言うより、僕の)コンピュータ環境に発生している、ある特殊な問題(あとで紹介する)の解決のために、Mac に関する問題をいろいろ調べることになった。長い間、Mac でシステムプログラミングをしていなかったので、OS 周りのことはすっかり忘れてしまっている。以下では僕の知識を整理し、また新たに得た知識を補いながら、 MacOS について今回いろいろ気がついたことを書いている。また僕の立場からの不満を述べているが、それらは、現在では解決されているかも知れない。(何しろ僕の環境は古い!)

unix のコマンドについての十分な知識を持っている読者を想定している。

最初に、この話の中で登場するコンピューターを紹介しておく。主役は mbook である。コマンドのプロンプトにはホスト名が使われているので、どこで実行されたか判るであろう。

mbook MacBook Pro 10.2 with Sierra (= ver.10.12.6)
2012 Model, 500GB SSD, 8GB memory

購入から 10 年近く経っているが、まだバリバリに使える。
OS のアップグレードはもはやできない。
MacBook を買い換える気はない。最後まで使い切るのが僕の主義である。

Apple は Mac を現代の文具として位置付けていると思う。しかも誰でも使える文具を目指している。
コンピューターとしての使いやすさを求める僕のような人間を、Apple はユーザーとしては想定していないと思える。

hebe Plan9/9front の下で動いているサーバーである。この Web のページも hebe でサービスしている。

maia Linux/Ubuntu の下で動いているサーバーである。外部にはサービスしていない。単に Linux ではどうなっているかを調べるために存在する。

DS_Store 問題

DS_Store とは何か?

普通の Mac のユーザー(コマンドを使わないユーザー)は ".DS_Store" の名のファイルの存在に気付かないと思う。ほぼ完全に隠されているからである。他方、ネットで ".DS_Store" を検索すると不満タラタラである。このファイルは Mac のユーザーには隠れて見え難いのであるが、他の OS のファイルシステムの中に作られると、露わに見える。多くのユーザは SD カードや USB メモリーなども使い、そこに Mac で作られたファイルをコピーしたりする。それを Windows など、Mac 以外の OS でみると ".DS_Store" を露わに見ることになり、これは何だ、気持ち悪い! と言うことになる。

僕の観察では、".DS_Store" は

従って、コマンドを使わない、普通の Mac のユーザーには見えない。

".DS_Store" は Finder からは見えないが、コマンド環境では存在が確認できる。

mbook$ ls -al
-rw-------    1 arisawa  staff      8196  7 12 06:41 .DS_Store

勝手に ".DS_Store" が作られるのは鬱陶しい。しかし、標準設定では、消しても消しても作られる。

このファイルは Finder のアイコン表示で使われているようだ。実際、表示されているアイコンをマウスで移動すると ".DS_Store" は更新される。 Finder をアイコン表示にし、アイコンをマウスで移動させることを許した場合、 移動後の位置情報の記憶が必要になる。そのアイコンの位置情報が ".DS_Store" に含まれているとか。僕はアイコン表示にしないので、このような機能は僕にとっては必要ない。

以前は Finder の環境設定で unix の隠しファイル(ピリオド '.' で始まるファイル)を見せるか否かの選択ができたのだが、現在はそのメニューは無くなっている。その代わりに

command + shift .
で切り替えができる。この方が良いと思うが、このやり方でも .DS_Store は隠されている。

実は Finder が隠しているのは '.' で始まるファイルだけではない。全ての開発環境が隠されていると言ってもよい1
そのことは Finder に表示される root ディレクトリと、ls コマンドで見える root ディレクトリを比較してみれば解る。

Finder はユーザー arisawa のエージェントとして動いている。このことは

mbook$ ps axl|grep 'Finder$'
  501   351     1   0   4  0  5757832 370408 -      S      ??   50:21.85 /System/Library/CoreServices/Finder.app/Contents/MacOS/Finder
mbook$ ps axl|head -1
  UID   PID  PPID CPU PRI NI      VSZ    RSS WCHAN  STAT   TT       TIME COMMAND
mbook$ id
uid=501(arisawa) gid=20(staff) ...
mbook$
で確認できる。501 は arisawa の UID である。従って arisawa では書き込み権限のないフォルダー(例えば システムフォルダー)には ".DS_Store" は作られない。

".DS_Store" は Mac にとって本当に必要なファイルなのであろうか? 代替え案を考えてみよう。
(a) Finder をアイコン表示にしたとしても、アイコンを移動した時にのみ ".DS_Store" を作れば良いではないか?
(b) アイコンを移動するニーズは極めて稀であって、情報をメモリー上に持てば良いではないか? Finder がリスタートすれば記憶が消えるが、気にするような話でもなかろう。単に元の位置に並べ替えられるだけである。

このうち (a) は実現されている:

mbook$ defaults write com.apple.desktopservices DSDontWriteNetworkStores True
mbook$ killall Finder
(b) を採用しなかったのは Apple の完璧主義によるものか?

ファイルやフォルダーのアイコンを Finder の中に表示するために、Finder は各ファイルのアイコンにアクセスしに行く。この問題は Finder のリスト表示でもカラム表示でも発生しブラウジングの速度を落とす。Finder がローカルなファイルシステムを表示している限り、問題にならないのであるが、ネット上にあるファイルをマウントして Finder に表示している場合には、ブラウジングの遅さが気になって来る。そのために、Apple は macOS 10.13 以降、やり方を少し変えたとか[2]... Finder の中にファイルやフォルダーのアイコンが見えるのは、クールで見栄えが良い。しかしそれはブラウジングの速度を著しく落とす代償の上に成立していることを忘れてはならない。

文献[1]に DS_STORE に関する丁寧な解説がある。


注 1: フラグを見よ

DS_Store file format

ファイル ".DS_Store" の先頭部分は、例えば次のようになっている1:

mbook$ xd -c .DS_Store |p
0000000  00 00 00 01  B  u  d  1 00 00 18 00 00 00 \b 00
0000010  00 00 18 00 00 00 10 0b 00 00 00 00 00 00 00 00
0000020  00 00 00 00 00 00 00 00 00 00 00 00 00 00 \b 00
0000030  00 00 \b 00 00 00 00 00 00 00 00 00 00 00 00 00
0000040  00 00 00 00 00 00 00 02 00 00 00 00 00 00 00 \n
...

いくつかの DS_Store について試したが、先頭8Bは共通ではないかと思える。
DS_Store についての書式は非公開らしい。推測するしかない。

先頭8Bに含まれる文字列 "Bud1" は DS_Store のマジックワードらしい。書式の推定については文献[4] と[5]が詳しい。また、たまたま見つけた[6]も面白い。


注 1: xd (hexa dump) は unix の od (octal dump) の 16進数版であり、plan9port に含まれている。xd に頼らなくても hexdump が Mac に備わっている。

参考文献

[1] DS_STOREファイルとは何ですか?
https://www.file-extension.info/ja/format/ds_store

[2] macOS で SMB ブラウジングの動作を調整する (2021)
https://support.apple.com/ja-jp/HT208209

[3] DS Store File Format
https://wiki.mozilla.org/DS_Store_File_Format

[4] DS_Store Format
https://metacpan.org/dist/Mac-Finder-DSStore/view/DSStoreFormat.pod

[5] Parsing the .DS_Store file format
https://0day.work/parsing-the-ds_store-file-format/

[6] 50年前に作られたメモリ管理アルゴリズム「Buddy memory allocation」(2016)
https://codezine.jp/article/detail/9325

リソースフォーク

リソースフォークに関しては、(僕には)解らないことが多すぎる。同様なことは文献[6]の著者も述べている。整理する必要があるが、まだできていない。特に HFS Plus が解らない。Apple の公式な文献が[5]にある。しかし名前付きフォークがよく解らない。

用語の問題

リソースフォーク(resource fork)は Macintosh 時代のファイルシステム (MFS) から受け継いだ概念で、データの本体(これを data fork と言う)以外のファイルについての属性を指す。Macintosh の場合には、例えばファイルのアイコンや、ファイルを開くのに使われるプログラムなどの情報を含む[1]。Macintosh はシングルユーザーのシステムであるから、アクセス制御に必要なファイルのオーナーなどの情報は持たなかったはずである。
この時代においては
file = "data fork" + "resource fork"
と理解して構わないと思う。フォルダーの概念もない時代であった。やがて Apple は MFS を "HFS (Hierarchical File System)" として改良した。当時はすでに MSDOS はフォルダーをサポートしていた。HFS は Mac にもフォルダーを導入するのが目的であったと考えられる。HFS は OS 7 まで続いた[4]。Apple は大容量の記憶装置に対応するために、 OS 8 で HFS Plus を導入した。

unix のファイルシステムでは初めからマルチユーザ環境が想定されていた。つまりサーバーとして設計されていた。マルチユーザ環境に必要なファイルに関する属性は整理され、それらはコマンド "ls -al" で見ることができる。また現在では(習慣的に)ファイル拡張子に意味を持たせ、そのファイルの内容の基本的な特徴を表すようになっていて、Macintosh のリソースフォークの機能の一部を担っている。

Apple を追い出された Jobs が立ち上げた NeXTSTEP は、BSD 系の unix の OS の下で構築された。Apple に復帰した Jobs は、NeXTSTEP をベースにした OS を新しい Macintosh として売り出し、現在に至っている。"OS X" つまり "OS 10" の名称は NeXTSTEP が Macintosh の "OS 9" の後継であることをアピールしている。なお現在では "OS X" よりも "MacOS" と表現される。

Jobs は OS X のファイルシステムとして、NeXTSTEP のファイルシステム UFS (unix file system) を採用せず、Macintosh OS 9 のファイルシステム HFS Plus を採用した。その理由は多分次のようなものだろう。

HFS Plus はセキュリティー概念の希薄なシングルユーザーのファイルシステムであるが、幸い、柔軟な構造を持っている。そこでリソースフォークの中で、unix 風の属性を定義したに違いない1
他方、リソースフォークの持つ柔軟性とセキュリティの破れとは紙一重である。Mac はうまく処理しているだろうか? 心配になる。

注 1. ユーザーが自由に編集可能なリソースフォークと完全に分離してアクセス制御の情報を持たない限り、セキュリティ上の重大な脅威になったはずである。HFS Plus では named fork が使えるそうであるから、それが使われた可能性があるが、文献ではよく解らない。

参考文献

[1] リソースフォーク
https://ja.wikipedia.org/wiki/リソースフォーク

[2] Unix File System
https://ja.wikipedia.org/wiki/Unix_File_System

[3] NEXTSTEP
https://www.operating-system.org/betriebssystem/_english/bs-nextstep.htm

[4] 第7回 HFS、HFS Plusの基本的概念【前編】
https://www.itmedia.co.jp/enterprise/articles/0707/24/news009.html
白山さん良い記事を書きますね---。

[5] HFS Plus Volume Format
https://developer.apple.com/library/archive/technotes/tn/tn1150.html

[6] Mac OS X Resource Forks
https://jonsview.com/mac-os-x-resource-forks

拡張属性(extended file attribute)

「拡張属性」の対立概念として「基本属性」があるべきだが、市民権を得ていない1。もともと単なる「属性」があり、それを拡張したので「拡張属性」と言われている。

unix 系の OS の場合、ファイル属性とは "ls -l" コマンドで見える情報である。例えば我が Mac では、ファイル foo.txt の属性は

-rw-r--r--  1 arisawa  staff  1572  8 23 12:19 foo.txt
である。個々のファイルには、
の属性が含まれている。

OS によっては、ここに含まれないファイル属性を持っている。それらは xattr コマンドでみることができる。

MacOS のマニュアルには

XATTR(1) BSD General Commands Manual XATTR(1)
The xattr command can be used to display, modify or remove the extended attributes of one or more files, including directories and symbolic links. Extended attributes are arbitrary metadata stored with a file, but separate from the filesystem attributes (such as modification time or file size). The metadata is often a null-terminated UTF-8 string, but can also be arbitrary binary data.
とある。

Mac の拡張属性には

などが含まれている。例えば、我が mbook では次のようになっている
mbook$ pwd
/Users/arisawa/Downloads
mbook$ ls -l rfc2555.txt
-rw-r--r--@ 1 arisawa  staff  42902  5 10  2020 rfc2555.txt
mbook$ xattr -l rfc2555.txt
com.apple.metadata:kMDItemWhereFroms:
00000000  62 70 6C 69 73 74 30 30 A1 01 5F 10 26 66 74 70  |bplist00.._.&ftp|
00000010  3A 2F 2F 66 74 70 2E 69 73 69 2E 65 64 75 2F 69  |://ftp.isi.edu/i|
00000020  6E 2D 6E 6F 74 65 73 2F 72 66 63 32 35 35 35 2E  |n-notes/rfc2555.|
00000030  74 78 74 08 0A 00 00 00 00 00 00 01 01 00 00 00  |txt.............|
00000040  00 00 00 00 02 00 00 00 00 00 00 00 00 00 00 00  |................|
00000050  00 00 00 00 33                                   |....3|
00000055
com.apple.quarantine: 0081;5eb72ffd;Google Chrome.app;22CCDB41-A3E7-431C-B0CB-6B8E4E12389F
mbook$ ls -l@ rfc2555.txt
-rw-r--r--@ 1 arisawa  staff  42902  5 10  2020 rfc2555.txt
	com.apple.metadata:kMDItemWhereFroms	   85
	com.apple.quarantine	   68
mbook$
この例では rfc2555.txt が Google Chrome を使って ftp://ftp.isi.edu/ からダウンロードされたことが読み取られるであろう。
Mac の TextEdit.app はこの情報に基づいて、ファイルを開くのを拒否することもある2:
何をバカなことを!
ファイルが何で作成されたにせよ、TextEdit.app がファイルを読み取って、何か良からぬことをやらかせば、TextEdit.app のバグではないか! バグであれば Apple の責任で修正するのが筋であろうに... Apple は何を心配しているのか全く理解できない。

他のアプリではどうか? iText Express、TeXShop.app、Xcode.app などでは

“main.txt” の開発元は未確認です。開いてもよろしいですか?
と一応開かせて貰える。

MacOS にマウントされている他のフォーマットのファイルシステムでは、ファイル foo の拡張属性は /._foo のように、ファイル本体と別のファイルに作られる。MacOS のユーザは、あたかも foo が拡張属性を持っているかのように見える。

拡張属性は僕にとっては邪魔以外の何物でもない。

拡張属性は xattr コマンドで除去できる:

xattr -c path
xattr -cr path		# act recursively


注 1: xattr に関するプログラミングマニュアルには "basic attribute" がある。
注 2: この場合、テキストファイルの拡張属性がいたずらしている可能性がある。拡張属性は除去できる。

SD カードへのコピー

mbook には SD カードのスロットが付いている。ここに SD カードを差し込むと SD カードの内容が(僕の場合には) /Volumes/MSD に見える。ここに MSD は僕が勝手に付けた名前である。

mbook$ ls -l foo.txt
-rw-r--r--@ 1 arisawa  staff  46  8  1 21:10 foo.txt
mbook$ cp foo.txt /Volumes/MSD/tmp
mbook$ ls -l /Volumes/MSD/tmp
-rwxrwxrwx@ 1 arisawa  staff        46  8  2 08:13 foo.txt
mbook$

この SD カードは diskutil コマンドでは "DOS_FAT_32" と表示されている。
Windows 系のファイルシステムには、個々のファイルに対するアクセス管理の情報がない。
unix で言えばファイルの owner や group の概念が存在しない。もちろんリソースフォークの属性も存在しない。
それにも関わらず、あたかも Mac のファイルのように見せかけているのは MacOS の仕事である。これを他の OS で見るとどうなるか?

Linux/Ubuntu の下で動くシステム maia で見ると

maia$ ls -al /media/arisawa/3237-6332/tmp
total 71232
drwxr-xr-x  2 arisawa arisawa    32768  7月  9 00:32 .
drwxr-xr-x 14 arisawa arisawa    32768  1月  1  1970 ..
-rw-r--r--  1 arisawa arisawa     4096  8月  2  2021 ._foo.txt
-rw-r--r--  1 arisawa arisawa       46  8月  2  2021 foo.txt
maia$
となる。

MacOS の cp コマンドは、コピー先(SD カード)に Mac の本来のファイルを作れないことを理解しているのだ。
Linux は unix 系で "." で始まる名前は隠されるので、目立たない。
ところが Windows 系では露わに見えるはずである。

SD カードでなくとも、mbook にマウントされた maia のファイルシステムについても同様である。

mbook$ ls -l foo.txt
-rw-r--r--@ 1 arisawa  staff  46  8  1 21:10 foo.txt
mbook$ cp foo.txt /n/maia/home/arisawa/tmp
mbook$ ls -al /n/maia/home/arisawa/tmp
total 6656
-rw-r--r--  1 arisawa  staff     4096  8  2 11:31 ._foo.txt
-rw-r--r--@ 1 arisawa  staff       46  8  2 11:31 foo.txt
mbook$

拡張属性を作らないシステムの設定が可能なら、僕はもっと幸せになるのだが...

Icon

Mac の

/System/Library/CoreServices/CoreTypes.bundle/Contents/Resources
の中には多数のアイコンが含まれている。例えば次のアイコンが含まれている:

BookmarkIcon.icns

それらは拡張子 .icns を持っている。.icon ではなく、複数形になっているのは、様々なサイズのアイコンの集合だからである。.icns を他の形式、例えば .png に変換したくば、Preview.app で可能である。
逆変換も Preview.app で可能である。「書き出す」のときに option キーを組み合わせれば画像形式のメニューが増え、その増えたメニューの中に .icns が含まれている。
ls コマンドで見た BookmarkIcon.icns のサイズ 1497744
-rw-r--r--  1 arisawa  staff   1497744  4 21  2017 BookmarkIcon.icns
は後の議論で使われる。

ファイルやフォルダーのアイコンを自分の好みのものしたければ、「情報を見る」を開いて、現在のアイコンの上に、好みのアイコンを重ねればよい:

フォルダー bar のアイコンを好みのものに変更したとせよ。ls コマンドで bar を見ると:
mbook$ ls -l@ bar
total 2928
-rw-r--r--@ 1 arisawa  staff  0  8 24 16:09 Icon?
	com.apple.FinderInfo	32
	com.apple.ResourceFork	1498054
mbook$
ファイル "Icon?" が新たに作られ、そのファイルは、2つの属性 com.apple.FinderInfocom.apple.ResourceFork を持っている。以下で話題にするのは com.apple.ResourceFork の方である。その前に "Icon?" の「?」を片付けておく。

ls コマンドによって "Icon?" と表示されているこのファイルの名称は、実は "Icon?" ではない。そのことは

mbook$ cd bar
mbook$ echo -n Icon? |hexdump
0000000 49 63 6f 6e 0d
0000005
mbook$ echo -n 'Icon?' |hexdump
0000000 49 63 6f 6e 3f
0000005
mbook$
で判る1。つまり "Icon" の末尾に、Macintosh 時代の改行コードであった "0d" が添えられて、それが ls コマンドでは 「?」として見えているのだ。そして
ls Icon?
としたとき、この 「?」はシェルのワイルドカードであり、任意の1文字にマッチする。従ってこのケースでは 0d コードにマッチする。何のために Apple はこんなつまらないトリックを?

BookmarkIcon.icns のサイズは 1497744 であったが、このアイコンを表示する "Icon?" の ResourceFork のサイズは、これより少し大きい 1498054 である。少し膨らませて Apple は何をしたのだろう? いくつか実験をしてみた:
(1) フォルダー baz を作り、そこに bar の "Icon?" をコピーしたら baz のアイコンになるか? ⇒ ならない
(2) barqux と名称変更したら "Icon?" は qux のアイコンになっているか? ⇒ なっている
(3) bar から bar.tar を作り、どこかにコピーして展開して bar を作ると、この bar のアイコンは "Icon?" になっているか? ⇒ なっている
以上の3つを満たす方法を考えてみたが、簡単な方法は思いつかない...

Mac のファイルやフォルダーは拡張属性を抱えているために、プログラム作りは unix に比べて一段と面倒になる。単なるコピーだけでも...

コマンド環境でも拡張属性を(ある程度)扱える。"Icon?" を例にとる。

mbook$ xattr -px com.apple.ResourceFork Icon? > res
mbook$ ls -l
total 11712
-rw-r--r--@ 1 arisawa  staff        0  8 24 16:09 Icon?
-rw-r--r--  1 arisawa  staff  4494162  8 25 12:58 res
mbook$
res のサイズが com.apple.ResourceFork の3倍になったのは res は 16進数で表現され
mbook$ head res
00 00 01 00 00 16 DB 94 00 16 DA 94 00 00 00 32
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
...
のようになっているからだ。

そこでこの res を他のファイルのアイコンに使ってみよう。

mbook$ touch baz.txt
mbook$ xattr -wx com.apple.ResourceFork "`cat res`" baz.txt
-bash: /usr/bin/xattr: Argument list too long
mbook$
うーむ... これは xattr コマンドのバグでしょう...


注1. Mac の ls コマンドは -b-B のフラグを持っており、ファイル名に含まれる制御文字を表示してくれる。

参考文献

文献[2] はあまり役に立たない。microsoft の説明である点で載せた。解説は文献[3]が良く纏まっている。
[1] 拡張ファイル属性
https://ja.wikipedia.org/wiki/拡張ファイル属性

[2] FAT、HPFS、NTFS ファイル システムの概要
https://docs.microsoft.com/ja-jp/troubleshoot/windows-client/backup-and-storage/fat-hpfs-and-ntfs-file-systems

[3] 第9回 Windowsのファイルシステムの概要とFATファイルシステム
https://www.atmarkit.co.jp/ait/articles/1507/16/news034.html

フラグ

MacOS には chflags と言うコマンドがある。
使い方は次のようなものである:

mbook$ pwd
/Users/arisawa
mbook$ ls -lO
total 184
drwxr-xr-x    9 arisawa  staff  -        306 11  7  2019 Applications
...
drwx------@   8 arisawa  staff  -        272  4 16  2017 GoogleDrive
drwx------@ 101 arisawa  staff  hidden  3434  7 17  2019 Library
drwx------    6 arisawa  staff  -        204  1 13  2017 Movies
...
mbook$
"ls" のオプション "-lO" の "O" は大文字の "O" (オー) である。Options の意味であろう。単なる "ls -l" と異なりオプションを表示する欄が現れる。
ここでは "Library" に対して "hidden" のオプションが指定されている。マニュアル1によると GUI から隠されるのだとか... 実際 Finder からは ホームディレクトリに存在するはずの「ライブラリ」が見えない。見えるようにするには
mbook$ chflags nohidden Library
とすればよい。実際「ライブラリ」が Finder に現れるのが確認できる。隠すには
mbook$ chflags hidden Library
である。

マニュアルによると、chflags の変更対象となるフラグには管理者特権を要するもがある。これらの情報は、一種の拡張属性ではあるが、コマンド xattr で変更できる fork とは別の fork に含まれていると推測する。

root ディレクトには Finder では表示されない多数の名前があった。そこではどうなっているか?

mbook$ cd /
mbook$ ls -lO
total 69
drwxrwxr-x+ 187 root     admin  sunlnk            6358  8  8 15:08 Applications
drwxrwxr-x@  16 root     admin  -                  544  9  2  2013 Developer
...
mbook$
実際、ここには hiddenrestricted など、多数のフラグ付きファイルの存在が確認できる。


注 1: man CHFLAGS(1)

Auto Save を巡る問題

Mac の "Auto Save" とは?

2つの意味がある。
(a) 終了する時に「保存」を選択しなくても自動的に変更内容がファイルに反映されるようにする。
iPhone や iPad ではこの機能は便利なのであるが、自動保存に頼っていると、プログラムの開発時に頭を傾げることになる。自動保存されたと思ってコンパイラを走らせても、まだ自動保存が完了していないことがあるのだ。手動で保存する方が確実で早い。
この意味での auto save は

システム環境設定 > 一般 > 書類を閉じるときに変更内容を保持するかどうかを確認
にチェッマークを入れるか外すかで制御できる。

(b) 編集中の途中の状態を自動的にファイルに保存する。
 この機能はクラッシュやフリーズした時に威力を発揮する。しかし保存のタイミングを自分で決められないのは辛い。特に、サーバのファイルをマウントして Mac から編集する時に困る。
この自動保存は止められない(多分)。この問題は後に議論する。

auto save はどうやら全てのアプリケーションに均一に作用している1

(a) のチェックマークは「バージョンを戻す」と関係している。
「バージョン」の意味が、チェックマークを入れている場合と、外している場合で異なる。
実験によると、チェックマークを外していると改行のタイミングでひとつのバージョンができる2
チェックマークが入っていると、保存のタイミングでひとつのバージョンができる。


注 1: これは誤り。プログラムの作り方に依存しているようだ。例えば、クロスプラットホームをうたう atom ( https://atom.io ) は自動保存はしていない。Apple が提供するライブラリに依存していると自動保存になるのではないかと思える。atom にもう少し僕の好みが入れば、僕が好きなテキストエディタになるのではないかと思えるのだが... あともう少し、頑張れ atom.
mi.app には「自動保存」のチェックボタンがある。これを外すと、(b) の意味での自動保存もしない。なお mi.app は多彩なメニューを含み幅広いユーザーに対応している。xcode.app は重くて嫌いという僕のようなユーザーには良いのではないか? 現在の我が愛用のエディタでもある。
注 2: チェックマークを外している場合のバージョン作成のタイミングはもっと複雑である。詳細は知らない。

実験での確認

どこかに foo.txt を作り、テキストエディタで開き、foo.txt への書き込みと

ls -l foo.txt
に表示される時刻を調べると、1文字でも変更が発生すると "ls -l" の時刻が変化するのを観察できる。

文献[2] によると

defaults write com.apple.TextEdit ApplePersistence -bool no
で autosave が off になると言うことだが、効果なし。

文献[3] によると

defaults write -g ApplePersistence -bool no
で autosave がグローバルに off になると言うことだが、これも効果なし。

ファイルの変更の履歴はどこに?

Apple は編集中の書類が、クラッシュやフリーズでやり直しになるのを避けたかったに違いない。
その場合、ファイルの変更の履歴をメモリー上に置くわけには行かない。ファイルとしてどこかにあるに違いないのであるが、(今の所)どこにあるのか分からない。

参考文献

[1] AUTO SAVE AND VERSIONS: AN OFTEN OVERLOOKED MAC FEATURE
https://eshop.macsales.com/blog/47415-auto-save-and-versions-an-often-overlooked-mac-feature/

[2] Disable Autosave and Enable "Save As..."
http://hints.macworld.com/article.php?story=20120604101520950

[3] How to completely disable auto-save and versions in Mac OS X Lion?
https://apple.stackexchange.com/questions/27544/how-to-completely-disable-auto-save-and-versions-in-mac-os-x-lion

別名で保存

"別名で保存 (save as)" が見えない。MacOS が Lion になってかららしい。

現在の標準設定では「別名で保存」のメニューは隠されている。
これは非常に不便。特に書き込み禁止領域のファイルをどこかに保存したい時。

キー操作

command+shift S
で別名で保存のメニューが出るが、常に表示させることもできる[1]。


[1] [Mac]「別名で保存…」を復活させる方法。
http://appdrill.net/62277/re-save.html

アクセス権がない

TextEdit.app はしばしば「アクセス権がありません。情報を見てください」と読み取りも書き込みも拒否することがある。ファインダーの情報やコマンドからの情報を見る限りアクセス権はあるはずである。しかも、システム領域や、他システムのファイルに対してではない。mbook の何の変哲も無い僕のファイルに対してである。今のところ、他のエディタではこの現象には出会っていない1

これはバグらしい。TextEdit.app の再起動で解決する。


注 1: 実際には他のテキストエディタでも発生するのかも知れない。僕は TextEdit.app が好きでよく使い、走りっぱなしになっている。そのためにバグが現れやすいと考えられる。ときどきリスタートさせればこんなことにはならないのではないか?
なお、Mac では、1つのテキストを編集するたびにテキストエディタのプログラムが動き出すのではなく、デモンとして動いている。そのおかげで同じファイルの編集画面を(間違えて)複数持つリスクは小さくなっている。この失敗は Linux ではよくやるのだ。

カスタムアクセス権

カスタムアクセス権が設定され保存させてくれない

僕は Linux のシステムを /n/maia/ に、Plan9 のシステムを /n/hebe/ にマウントしている。ところが /n/hebe/ の僕の領域 /usr/arisawa/doc/ に TextEdit.app のファイルを保存しようとすると「アクセス権がありません」と拒否される。
情報を見ろと言われるので、見ると

「カスタムアクセス権が割り当てています」
/n/maia の場合には
「読み出し/書き込みができます」
と表示されている。

他のアプリ、例えば mi.app の場合には保存させて貰えるので、TextEdit.app の個性であろう。コマンドによる hebe へのコピーは可能である。

この違いはどこから来ているのか? また「カスタムアクセス権」の内容は何か? どこで設定されているのか?

hebe の場合 /n/hebe の「情報」は

容量: 0 バイト
空き領域: 0 バイト
使用領域: 0 バイト
保存できないトラブルは、これが原因か?

maia は正しく表示されている。

容量: 240.84 GB
空き領域: 206.97 GB
使用領域: 33,866,063,872 バイト(ディスク上の33.87 GB)
この情報はどのようにして mbook に渡されたのだろう。かなり高度な情報なのだ。

df の出力が参考になる。

mbook$ df
Filesystem            512-blocks      Used Available Capacity iused      ifree %iused  Mounted on
arisawa@maia:/         470392688  42110616 404247976    10%  344687   14663057    2%   /n/maia
mount_macfus@macfuse9          0         0         0   100%       0          0  100%   /n/hebe
mbook$
他にも情報は表示されているが、今の話に関係のない部分は捨てた。
Used とは使用されたブロック数、Available は(一般ユーザーにとって)利用可能なブロック数である*。多分 TextEdit.app は丁寧に作られていて、/n/hebe には利用可能な容量はないと判断している可能性がある。実際には膨大な利用可能な記憶域が存在するのだが TextEdit.app の知るところではない ...

注 *: sys/mount.h に含まれる struct statfs の中のコメントを見よ。

df の情報はどのようにして得られたか? 幸い df のソースコードが存在する:
https://opensource.apple.com/source/file_cmds/file_cmds-45/df/df.c.auto.html
df.c を読むに、関数 getmntinfo() を通じて UsedAvailable の情報を得ている。
getmntinfo() の情報はカーネルから来る。カーネルは Plan9 サーバー hebe に問い合わせるのであるが、その仲介は plan9port (p9p) の配布ファイルに含まれる 9pfuse が行なっているはずである。
/dev を見ればデバイスファイル

macfuse0, macfuse1, ..., macfuse63
が見える。これらはカーネルが提供する交渉の窓口であり、9pfusemacFUSE のコミュニケーションに使われている。 9pfuse は、この窓口を通じて macFUSE の要請を受け、hebe に利用可能な容量を問い合わせたはずである。

p9p の 9pfuse では、それをどのように処理したか?

plan9port の配布ファイルの /src/cmd/9pfuse/main.c
の中に著者の Russ のコメントがある。

/*
 * Statfs.  Send back information about file system.
 * Not really worth implementing, except that if we
 * reply with ENOSYS, programs like df print messages like
 *   df: `/tmp/z': Function not implemented
 * and that gets annoying.  Returning all zeros excludes
 * us from df without appearing to cause any problems.
 */
void
fusestatfs(FuseMsg *m)
{
	struct fuse_statfs_out out;

	memset(&out, 0, sizeof out);
	replyfuse(m, &out, sizeof out);
}
つまり macFUSE が要求する空き領域などの情報に対して全て 0 として返事することにしたのである。

Plan9 には df コマンドは存在しない。なぜか? この理由は Plan9 のリリースにあたって開発者が説明している:
記憶メディアは安く大容量になり、サーバーは使い切れないほどの容量を備えるようになった。ユーザーが df コマンドで容量を気にする時代は去ったのだ[1]。とか...
我が家の Plan9 サーバーも確かにそうである。僕が残りの人生の間に満杯になることはないであろう。

なお、誤解がないよう... Plan9 ではファイルシステムの残りの容量を見れないと言っているのではない。これはファイルシステムを提供するシステム管理者の仕事であって、ユーザーが気にする話ではないと言っている。

問題の解決策

Russ のやり方で、MacOS が現実に問題を引き起こしている。そこでこの問題をどのように解決したら良いかを考えて見る。3つの方向が考えられる。
(a) fusestatfs() を改造してサーバーの正しい情報を教えてやる。しかし、これは Plan9 サーバー側にも手入れが要求される。何しろ Plan9 のファイルシステムは OS に固定されていなくて、ユーザーの作成を許すのである。その結果、現実に多様なファイルシステムが存在する。fusestatfs() の要求を受け止めるプロトコルが存在しない。
(b) fusestatfs() を少し改造して、MacOS を騙す。残り容量を適当に返すのである。これで MacOS 側は完全に騙せて、残り容量があるものとして、ファイルを作らせてくれるかも知れない1
(c) 第三の方法は既存のソフトウェアをそのままに使って、リモートシステムの編集方法を変える。つまり直接編集しないで、僕の mbook に一旦コピーして、そこで編集し、終わったらリモートシステムにコピーバックする。さすがにコピーコマンドはテキストエディタのように馬鹿丁寧にリモートシステムの残り容量を吟味しない。

僕は現在、第三の方法を採用している。コピーとコピーバックの2つが発生するので、やり難そうに思えるかも知れないが、簡単なシェルスクリプトで実現できる。この方法は大きなメリットを持っている。MacOS はさすがにローカルなファイルシステムを使っている限り、良くできている。不満はない。特に作成中のデータを失わないように細心の注意と努力が払われている。なにしろ Mac は現代の文具である。そのために必要な要件を Apple はよく考えていると思う。作品を作るのに没頭し、保存し忘れ、システムクラッシュやフリーズによる作品が喪失することを Apple は恐れたのであろう。従ってリモートのファイルをローカルにコピーし、そこで編集することは Mac のこの特性を享受することになり、大きなメリットを持っている。

このシェルスクリプトの名は edit である。特殊な使い方は

mbook$ man xxx | edit
で、xxx のマニュアルが(edit で指定された)テキストエディタでみることができる。これは非常に良い。man コマンドの output を Terminal で見るなどというのは歴史の遺物であって、バカバカしくてやっておれない。


注 1: 我が家の WebDav は自作のコードの下で動いている。その WebDav で実験したところ騙しきれない。TextEdit.app は、保存したテキストが誰かに上書きされたと言うのだ。Mac 恐るべし!

参考文献

[1] Plan 9 from Bell Labs
https://9p.io/sys/doc/9.html

システム保護

僕の MacBook Pro は古いので バージョンは 10.12.6 (Sierra) である。もうバージョンアップはさせてくれない。しかしバージョンアップをしなくてもよいなら、それが一番幸せである。なにしろ、Mac はバージョンアップを重ねるたびに使い難くなる。僕は Mac の過剰と思える保護機能にうんざりしている。Sierra においてすらそうだ。

領域の区分け

僕はこれまで
(a) Apple が責任を持って管理する領域
(b) サードパーティのアプリケーションが使ってよい領域
(c) 個人ユーザーが自由に使える領域
の区分を Apple がどのように考えているのかはっきりしていなかった。
最近(2019)になって、Apple の見解が正式に示されたようだ[6]。
それによると

は Apple 占有領域である。ただし /Applications は Apple 以外との共存を許す。

サードパーティのアプリが使える領域は

である。

残りはユーザーが自由に使えると解釈したいが... 僕は /home を作って /Users にリンクを貼っていたが、いつのまにか消されていた。どうやら /home は Apple が何かに使う予定らしい1

領域区分をはっきりさせておくことは良いことだ。僕は管理者権限でソフトをインストールしたくはない。面倒だからと言うのではなく、危険だからである。Mac はサーバーとしては設計されていない。サーバーとしてなら、別の選択肢を考えるべきである。Mac の設計コンセプトは現代の文具であって、サーバー管理の作法を Mac に持ち込むことは間違っている。
文具


注 1: もう使われている。df コマンドの出力を見よ。

カーネル機能拡張

文献[5]によると

OS X Yosemite以降、ドライバなどのカーネル機能拡張には、特定のアップルが承認したコード署名をする必要がある。 その為、開発者は、アップルへ開発者IDを要求する必要がある[11] 。コード署名されていない機能拡張が存在する場合、カーネルはシステムの起動を拒否し、代わりに禁止記号を表示する。このメカニズムは、 "kext署名"と呼ばれ、システム整合性保護に統合された[12]。macOS High Sierraからは、カーネル機能拡張に署名がある場合でも、初回起動時にユーザによる許可を必要とするSecure Kernel Extension Loading(SKEL)が導入された[13]。
とある。文献[5]の根拠となっているのは文献[6]だとおもう。ここには
Kernel Extensions Must Be Signed
Kernel extensions must be signed with a Developer ID for Signing Kexts certificate.
とある。確かに、いい加減なドライバーは大問題である。(Mac に限らず) OS はドライバーをカーネル空間に持っている。その場合バグの破壊力が大きすぎる。さらにネットでダウンロードしたドライバには、どのような罠が仕掛けられているか判らない。Apple が厳格な統制をしたがるのは理解できる。

カーネル機能拡張の問題点は、銀行などの窓口業務に例えて見ればよく理解できるだろう。なぜ銀行などがカウンターを備えてそこに窓口を設けているのか? 部外者を事務空間からシャットアウトするためである。カーネル拡張とは、利用者が窓口を通さずにセルフサービスで銀行の資源を操作するに等しい。このようなやり方は昔からパーソナルコンピュータ界で平気で行われていた。そして今も行われている。もっとも致し方ない面もあった。新しい種類のハードウェアが次々と誕生し、OS の提供側がそれに追いつかず、ハードウェアメーカーがドライバを作るスタイルが定着して行ったのである。Microsoft はそれにすっかり安住している。Apple はこのスタイルと決裂すると宣言したのである(と思いたい)。

次は文献[8]からの引用である:

重要: macOSではkextは推奨されなくなりました。kextはオペレーティングシステムの整合性と信頼性を危険にさらすため、ユーザはむしろカーネルの拡張を必要としないソリューションを好むでしょう。

macOS Mojave からは、 Apple の署名のない kext は特殊な方法を使わないと使用できなくなっているらしい[7]。

僕の mbook にも気づかないうちに kext がインストールされている。

mbook$ cd /Library/Extensions
mbook$ ls -l
total 0
drwxr-xr-x  3 root  wheel  102  6 13  2014 ACS6x.kext
drwxr-xr-x  3 root  wheel  102  6 28  2016 ATTOCelerityFC8.kext
drwxr-xr-x  3 root  wheel  102  6 28  2016 ATTOExpressSASHBA2.kext
drwxr-xr-x  3 root  wheel  102  6 28  2016 ATTOExpressSASRAID2.kext
drwxr-xr-x  3 root  wheel  102  8 21  2013 ArcMSR.kext
drwxr-xr-x  3 root  wheel  102  9 12  2014 BJUSBLoad.kext
drwxr-xr-x  3 root  wheel  102  2 16  2016 CIJUSBLoad.kext
drwxr-xr-x  3 root  wheel  102  9  1  2013 CalDigitHDProDrv.kext
drwxr-xr-x  3 root  wheel  102  8 15  2014 HighPointIOP.kext
drwxr-xr-x  3 root  wheel  102  8 15  2014 HighPointRR.kext
drwxr-xr-x  3 root  wheel  102  2  2  2021 Plser.kext
drwxr-xr-x  3 root  wheel  102  9 10  2020 ProlificUsbSerial.kext
drwxr-xr-x  3 root  wheel  102  4 28  2014 PromiseSTEX.kext
drwxr-xr-x  3 root  wheel  102  8  4  2016 SoftRAID.kext
mbook$ ls -lO

他に kext を組み込んだアプリケーションがある。Apple 以外のアプリで kext を使用しているものは次のようにして見ることができる:

mbook$ kextstat|grep -v com.apple
Index Refs Address            Size       Wired      Name (Version) UUID <Linked Against>
  144    3 0xffffff7f833da000 0xf0000    0xf0000    org.virtualbox.kext.VBoxDrv (6.0.18) 96270992-93C7-3FCE-9AA2-37A8A7DCC926 <7 5 4 3 1>
  146    0 0xffffff7f834ca000 0x8000     0x8000     org.virtualbox.kext.VBoxUSB (6.0.18) B9318296-40DB-31E6-AD39-0806529AA3F2 <145 144 43 7 5 4 3 1>
  147    0 0xffffff7f834d2000 0x5000     0x5000     org.virtualbox.kext.VBoxNetFlt (6.0.18) 336536F2-3A17-3603-9F72-77A72B523230 <144 7 5 4 3 1>
  148    0 0xffffff7f834d7000 0x6000     0x6000     org.virtualbox.kext.VBoxNetAdp (6.0.18) D7DDC615-34B1-3AFF-BD69-D60CF308B972 <144 5 4 1>
  150    0 0xffffff7f834ec000 0x17000    0x17000    io.macfuse.filesystems.macfuse (2066.16) DB178900-CE62-3448-ACCA-B1798E2D045C <7 5 4 3 1>
mbook$

このリストの最後にある mscfuse は後で説明する。

ドライバーのバグ(あるいは悪意のドライバー)の影響範囲が限定されれば問題は解決するのだが、現在の技術ではそこまでやれていないだろう。

参考文献

[1] Mac OSX 10のカスタムアクセス権を簡単に解除する方法
https://anicame.com/post-2121/

[2] Macでファイル、フォルダ、またはディスクに対するアクセス権を変更する
https://support.apple.com/ja-jp/guide/mac-help/mchlp1203/mac

[3] Mac のシステム整合性保護について
https://support.apple.com/ja-jp/HT204899

[4] Catalinaでファイルシステムがこう変わる
https://news.mynavi.jp/article/osxhack-242/

[5] システム整合性保護
https://ja.wikipedia.org/wiki/システム整合性保護

[6] System Integrity Protection Guide
https://developer.apple.com/library/archive/documentation/Security/Conceptual/System_Integrity_Protection_Guide/Introduction/Introduction.html

[7] FUSE for macOS will not load with SIP enabled
https://discussions.apple.com/thread/251861999

[8] macOSのカーネル機能拡張
https://support.apple.com/ja-jp/guide/deployment-reference-macos/apd37565d329/web

[9] Mac で App を安全に開く
https://support.apple.com/ja-jp/HT202491

macFUSE

可能性のあるカーネル拡張技術の方向は fuse である。fuse はユーザープログラムによってカーネルに組み込まれていないファイルシステムを実現する技術である。もちろん fuse 自体はカーネルを拡張している。 fuse はカーネルにユーザープログラムとの交渉窓口を開く。現在の MacOS の場合には、その窓口は

/dev/macfuse0, /dev/macfuse1, ..., /dev/macfuse63
まで準備されている1。ユーザープログラムはこの窓口を通じてリクエストとその結果を受け取る。

僕の環境では fuse は我が家のサーバーのファイルシステムを mbook にマウントするのに使われている。サーバーは Plan9 の下に動いているものと、Linux や FreeBSD で動いているものがある。後者は sshfs でマウントされるので、サーバーを持っている人は使ったことがあるだろう。

ネットの記事を見ていると fuse について Linux 由来であるとの解説が支配的である。「由来」をどのような意味に取るかが問題である。カーネルに組み込まれていないファイルシステム(これをユーザーランドのファイルシステムと言う)を使い出したのは Plan9 である。発祥の地は Bell 研究所である。このアイデアを Linux で実現したいとする人たちが現れ、Linux のカーネルの中に必要なインターフェースが組み込まれた。Mac へも fuse の技術が Google によって MacFUSE として持ち込まれた2
つまりアイデアは Plan9 由来である。さらにコードを眺めていると、コードの骨格も Plan9 のものと同じである。まあ、同じアイデアに立てば、誰が書いても同じようなコードになるのだろう。コードのコピペで「由来」を判断すれば、Linux の fuse は独自のものだったかもしれない。


注 1: Linux の場合には /dev/fuse だけが見える。Mac のは沢山あるのでサービスが良いのかと言えば違う。Linux のはお客さんが窓口に見えれば、直ちに新しい窓口を作ってくれる魔法の窓口なのである。エレガントだね。
注 2: MacFUSE は Linux の fuse のコピペだったか疑わしい。なぜなら、MacFUSE が発表される5年ほど前に、Bell 研の Plan9 チームの殆どが一斉に Google に移ったからである。そしてこの頃は Apple と Google は仲が良かった。

WebDav

iCloud と WebDav との関係

Apple の iCloud が WebDav の技術に基づいているのか否かは定かではない。iCloud が行われる前のサービス(iDisk)が WebDav に基づいていたと WikiPedia に解説されている[1]。この解説は信じてもよいのではと思う。

iCloud のファイルを Finder から見えるようにするには

Finder > 環境設定 > サイドバー
の iCloude Drive にチェックを入れればよい。

Finder を通じて Mac を WebDav クライアントにするには

Finder > 移動 > サーバーへ接続 > サーバーアドレス
へと続けばよい1。我が家の Pegasus 下の WebDav もこの方法で使える。

Pegasus 下の WebDav サーバに来る Mac の Finder からのリクエストには

User-Agent: WebDAVFS/3.0.0
とある。つまり WebDAVFS が Apple による正式名称である。

URL http://ar:8080/dav/ で指定される WebDav サーバーに接続したままで ps を実行すると

mbook$ ps ax | grep webdav
 1438   ??  Ss     0:00.58 webdavfs_agent -o noautomounted -o browse -o nordonly -a4 -vdav http://ar:8080/dav/ /Volumes/dav
 1466 s001  R+     0:00.00 grep webdav
mbook$
つまり WebDAVFS の接続先が見える。iCloud は動いているので、もしも WebDAVFS が iCloud と接続していれば、ps で見えたはずである。従って、 WebDAVFS は iCloud には接続していないと断言してよい。

iCloud に関係のあるプロセスは(全部ではないかもしれないが)次のようにして見える:

mbook$ ps axl | grep -i icloud
  501   472     1   0  20  0  2869052  18316 -      S      ??    0:07.94 /usr/libexec/SafariCloudHistoryPushAgent
  501  1561     1   0   4  0  2724284  42060 -      S      ??    0:00.21 /System/Library/PrivateFrameworks/AOSKit.framework/Versions/A/XPCServices/com.apple.iCloudHelper.xpc/Contents/MacOS/com.apple.iCloudHelper
  501  1570  1530   0  31  0  2453252   2124 -      S+   s002    0:00.02 grep -i icloud
mbook$
他方 netstat の情報も参考になる:
mbook$ netstat -n|grep tcp
tcp4       0      0  192.168.0.249.51852    162.125.36.2.443       ESTABLISHED
tcp4       0      0  192.168.0.249.51843    17.57.145.54.5223      ESTABLISHED
tcp4       0      0  192.168.0.249.51841    17.57.152.23.993       ESTABLISHED
tcp4       0      0  192.168.0.249.51838    17.188.166.20.5223     ESTABLISHED
tcp4       0      0  192.168.0.249.51835    162.125.35.134.443     ESTABLISHED
tcp4       0      0  192.168.0.249.51834    108.177.97.108.993     ESTABLISHED
tcp4       0      0  192.168.0.249.51829    17.57.152.23.993       ESTABLISHED
...
mbook$
これと whois データベースを付き合わせると
162.125.0.0 - 162.125.255.255 は DropBox
17.0.0.0 - 17.255.255.255 は Apple
108.177.0.0 - 108.177.127.255 は Google
が判る。さすがに Apple は凄い。Google が少なすぎると思えるが、他にもあるのだろう。

文献[3]によるとポート 5223 について

iCloud DAV サービス (連絡先、カレンダー、ブックマーク)、プッシュ通知、FaceTime、iMessage、Game Center、フォトストリーム
とある。つまり(説明によると) iCloud でも WebDav が使われている。それにも関わらず WebDAVFS がそのために使われていない。どう解釈したらよいのだろう?

Apple にとって、ポート 5223 のサービスはトップクラスの重要性を持っていると考えてよい。従って十分な信頼性が要求される。ところが WebDav の標準的なプロトコルは貧弱である。セキュリティ的にも問題で、効率も悪く、Apple の現実的なニーズに応えられそうもない(と僕は感じる)。Apple は WebDav のプロトコルを独自に拡張して iCloud に適用しているのだろうと推測する。

rfc4331 には、クライアントからサーバーへの要求「空き容量を知らせてほしい」

<D:propfind xmlns:D="DAV:">
 <D:prop>
  <D:quota-available-bytes/>
  <D:quota-used-bytes/>
 </D:prop>
</D:propfind>
が例示されている。

しかし、 WebDAVFS からは、次のような要求が来る。

<?xml version="1.0" encoding="utf-8"?>
<D:propfind xmlns:D="DAV:">
<D:prop>
<D:quota-available-bytes/>
<D:quota-used-bytes/>
<D:quota/>
<D:quotaused/>
</D:prop>
</D:propfind>
ここに現れる quota とか quotaused とかは、古い WebDav クライアントとの互換性を保つために入っているらしい2


注 1: コマンドラインからの使い方は
mtpt=....  # your favorite mount point
mount_webdav -si http://ar:8080/dav $mtpt
のようなものである。Finder を通すよりも、こちらの方がレスポンスが良い。

注 2: Apple が公開している webdavfs ソースコードの中のコメントから判る。


参考文献

[1] iDisk
https://ja.wikipedia.org/wiki/IDisk

[2] How to Stop Finder WEBDAVFS from requesting .hidden, ._Directory , ._FileName files after Remotes File System Mount has happened?
https://discussions.apple.com/thread/3786731

[3] Apple ソフトウェア製品で使われている TCP および UDP ポート
https://support.apple.com/ja-jp/HT202944

開発環境

C の include file

普通の unix 系のシステム(例えば Linux)では C のインクルードファイルは /usr/include に置かれる。ところが我が環境では

mbook$ ls /usr/include
ls: /usr/include: No such file or directory
mbook$
となっていた。

インクルードファイルを探すことにした。例えば最も初等的な stdio.h はどこにあるか?

/usr/local/include
なぜか Plan9 関係のもここにある。しかも新しい。
u.h が 2017 年。いつここにコピーされたか?

/opt/local/include
ここには何も無い

/Developer/Headers
ここは Apple 関係限定

/Developer/usr/include
ここは gcc、しかも殆ど何もない

/Developer/usr/llvm-gcc-4.2/include
殆ど何もない

/sw/include
ここが一番近い。しかも apt-pkginclude がある。
しかし stdio.h が無い

/Developer/SDKs/MacOSX10.6.sdk/usr/include
ここに stdio.h がある。

他にも SDK のバージョンごとに存在する:

mbook$ ls -l /Developer/SDKs/
total 14728
-rw-r--r--@ 1 root     wheel      483  2 20  2013 MEMO.txt
drwxr-xr-x  7 arisawa  staff      238 12  2  2011 MacOSX10.4u.sdk
drwxr-xr-x  7 root     wheel      238  6 30  2009 MacOSX10.5.sdk
drwxr-xr-x  7 root     wheel      238  8  3  2009 MacOSX10.6.sdk
-rwxr-xr-x@ 1 arisawa  wheel  1941316  2 20  2013 MacOSX10.7.sdk
-rwxr-xr-x@ 1 arisawa  wheel  1825968 12 17  2013 MacOSX10.8.sdk
mbook$
一番新しいバージョンのはどこにあるのか?
mbook$ ls -l /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs
total 8
drwxr-xr-x  5 root  wheel  170  1  1  1970 MacOSX.sdk
lrwxr-xr-x  1 root  wheel   10  1 19  2018 MacOSX10.13.sdk -> MacOSX.sdk
mbook$
ここに含まれている!

補注: 実は locate コマンドを使えば、もっと簡単に見つかったはずである。単に

locate include|grep '/stdio.h$'
を実行すればよい。Mac では locate データベースのデフォルトは off である。容易に on にできる。locate コマンドを実行すれば、on にする方法が表示される。Mac には Spotlight があるではないか? mdfind があるではないか? しかし output が多すぎて役に立ったことが無い。

ところで今頃こんな話をしているのは、C のコンパイラを使っていて、インクルードファイルが見つからないとは言われたことがなかったからである。Mac におけるインクルードファイルの配置は特殊である。それなのにどのようにして C のコンパイラはその位置を知ったのか。尤も、インクルードファイルの問題は C のプリプロセッサ cpp の問題である。従って僕の疑問は「cpp はどのようにしてインクルードファイルの位置を知ったのか?」にある。

この疑問の答えは次の URL に存在する:
https://gcc.gnu.org/onlinedocs/cpp/Search-Path.html
これによると cpp のコンパイル時に指定できるらしい。その内容は

cpp -v /dev/null -o /dev/null
を実行して見れば判るとか... 我がケースでは次のようになっている:
mbook$ cpp -v /dev/null -o /dev/null
Apple LLVM version 9.0.0 (clang-900.0.39.2)
Target: x86_64-apple-darwin16.7.0
Thread model: posix
InstalledDir: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin
 "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang" -cc1 -triple x86_64-apple-macosx10.12.0 -Wdeprecated-objc-isa-usage -Werror=deprecated-objc-isa-usage -E -disable-free -disable-llvm-verifier -discard-value-names -main-file-name null -mrelocation-model pic -pic-level 2 -mthread-model posix -mdisable-fp-elim -fno-strict-return -masm-verbose -munwind-tables -target-cpu penryn -target-linker-version 305 -v -dwarf-column-info -debugger-tuning=lldb -resource-dir /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/clang/9.0.0 -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.13.sdk -I/usr/local/include -fdebug-compilation-dir /Users/arisawa/dist/p9git-2021 -ferror-limit 19 -fmessage-length 145 -stack-protector 1 -fblocks -fobjc-runtime=macosx-10.12.0 -fencode-extended-block-signature -fmax-type-align=16 -fdiagnostics-show-option -fcolor-diagnostics -traditional-cpp -o - -x c /dev/null
clang -cc1 version 9.0.0 (clang-900.0.39.2) default target x86_64-apple-darwin16.7.0
ignoring nonexistent directory "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.13.sdk/usr/local/include"
ignoring nonexistent directory "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.13.sdk/Library/Frameworks"
#include "..." search starts here:
#include <...> search starts here:
 /usr/local/include
 /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/clang/9.0.0/include
 /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include
 /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.13.sdk/usr/include
 /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.13.sdk/System/Library/Frameworks (framework directory)
End of search list.
# 1 "/dev/null"
# 1 "<built-in>" 1
# 1 "<built-in>" 3
# 330 "<built-in>" 3
# 1 "<command line>" 1
# 1 "<built-in>" 2
# 1 "/dev/null" 2

Apple LLVM version 9.0.0 (clang-900.0.39.2)
Target: x86_64-apple-darwin16.7.0
Thread model: posix
InstalledDir: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin
clang: warning: argument unused during compilation: '-traditional' [-Wunused-command-line-argument]
mbook$
この出力には、実際に C コンパイラで使われているインクルードファイルだけが表示されているはずで、この方法を知っていれば苦労はしなかったはずである。

ところでプログラムの開発においてインクルードファイルの参照は不可欠である。unix や Linux であれば、いくつかの固定した場所(例えば /usr/include/usr/local/include) に置かれているので探すのに苦労は要らない。
僕がよく使う、Plan9 のシステムでも、もちろん置き場所のルールがある。さらにテキストエディタの中で簡単に探せる:

#include <xxx.h>
の "xxx.h" をマウスでクリックすると、直ちに xxx.h を参照できる。Mac では、類似の機能は Xcode.app にすらない。

Mac は複雑である。cpp -v の output を見る限り、インクルードファイルの置き場所として、少なくとも次の4つは含まれている。

/usr/local/include
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/clang/9.0.0/include
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.13.sdk/usr/include
他にも何箇所かあるが、規則がはっきりせず、その全てを列挙する元気がない。
そこで妥協的な解決策であるが、この4つについてインクルードファイルへのアクセスを容易にするためのプログラムを作ることにした。簡単なシェルスクリプトであるからここに載せておく:
#!/usr/local/plan9/bin/rc
# you need plan9port and also lr and 9xa
# look http://p9.nyx.link/netlib for lr and 9xa
#

rfork e

usage='usage: hgrep [-v] pattern'

vflag=0
while(~ $1 -*){
switch($1){
case -v
  vflag=1
  shift
case -*
  echo $usage
  exit
}}

a=`{cpp -v /dev/null -o /dev/null > /dev/null |[2] grep '/include$'}
if(~ $vflag 1){
  for(b in $a)
    echo $b
}

if(! ~ $#1 1)
  exit

t=$1
lr -f $a | 9xa grep -n $t
このシェルスクリプトは、与えられた文字列パターンが含まれているヘッダファイルを探し出す。強力な武器になるはずである。

なお、シェルスクリプトは Plan9 の rc である。可読性に富み、シンプルかつ強力で、僕は bash を使う元気がない。
また lr9xa については

に解説されている。

マニュアル

開発に必要なマニュアルがイントールされていないことに気づいた:

mbook$ man listxattr
No manual entry for listxattr
mbook$
しかし存在するのだ:
mbook$ lr /Applications/Xcode.app/Contents | grep listxattr
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/share/man/man2/flistxattr.2
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/share/man/man2/listxattr.2
mbook$

ここに lr は僕が Plan9 用に作ったプログラムである。lr とは ls recursively の意味で、ファイルを探す時に威力を発揮する。lr は (Plan9port の下で) Mac や Linux などへも移植できる。次の URL で手に入る:
http://p9.nyx.link/netlib/cmd/lr/
lr は unix の ls -1R と違って、1つの行に1つのパスの考えに徹している。その方が出力を再利用しやすい。出力を grep に渡せるのは、そのように設計されているからだ1

マニュアルへのリンクを作ることにした:

mbook$ ls -l /usr/local/xcode
total 8
lrwxr-xr-x  1 arisawa  wheel  108  8  3 07:25 man -> /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/share/man
mbook$
それで
mbook$ lr -L /usr/local/xcode | grep listxattr
/usr/local/xcode/man/man2/flistxattr.2
/usr/local/xcode/man/man2/listxattr.2
mbook$
そして環境変数 MANPATH を修正して
mbook$ man listxattr
...
mbook$
OK

ところで

mbook$ manpath
/usr/local/xcode/man:/usr/share/man:/usr/local/man:/usr/local/texlive/2017/texmf-dist/doc/man
mbook$

mbook$ echo $MANPATH
/usr/local/xcode/man:/usr/man:/usr/share/man:/usr/local/man:/usr/X11R6/man:/usr/local/texlive/2017/texmf-dist/doc/man
mbook$
と必ず同じ内容を表示するわけではない。manpath が表示するのは man が実際に読み取って利用しているマニュアルのパスである。そこにマニュアルが存在しなければ表示しない。
mbook$ ls -l /usr/bin/man*
-r-xr-xr-x  1 root  wheel  70128  7 15  2017 /usr/bin/man
lrwxr-xr-x  1 root  wheel      3  8 20  2017 /usr/bin/manpath -> man
mbook$
リンクが上手く使われている。manpathman とは別プログラムになっていないことが、manpathman の実際に迫れる秘訣である。

ところでマニュアルは、今やローカルに持たなくても、ネットで見れるのだね。
例えば http://www.manpagez.com/ がある。この中の rename を眺めていると、次の説明が気になった:

The rename() system call guarantees that an instance of new will always exist, even if the system should crash in the middle of the operation.
うーむ、首を傾げてしまう。一般に、ファイル操作中のシステムクラッシュは、操作対象のファイルだけではなく、最悪の場合にはシステムの全てのファイルを消失させる2。システムを二重化せずに、どのような仕組みになって入れば、このようなことが可能なのか? 知りたいものだ。


注 1: この場合
locate listxattr|grep man
の方が確実で、かつ早かったであろう。(Mac の locate コマンドは、標準では off になっている)
注 2: Plan9 ではシステムの二重化をしないで、この問題に対して全く別のアプローチをとる。

Apple と Open Source Community

今回、macFUSE の動作を知りたくて、いろいろ調べることになった。理由は Apple がカーネル拡張を廃止するとアナウンスしているからである。ならば具体的にどうするつもりであるか? カーネル拡張に依存していた企業は、この間、Apple の新しい方針に沿ったドライバを作ってきたはずである。その際、Apple からの指導や、情報が企業のエンジニアに与えられたはずである。ところが僕はそのような情報を持っていない。どこから手に入れたら良いのか?

僕は macFUSE を調べることにした。macFUSE もカーネル拡張を使っていた。macFUSE のソースコードの変化を調べれば答えが得られるだろうと期待したのである。僕が持っている関連するソースコードは macFUSE の前に使われていた osxFUSE までである。このソースコードは 2017 年に入手している。

ところがどれだけ調べても macFUSE のソースコードが見つからないのである。そうこうしている間に次の2つの記事[1,2]を見つけた。
文献[1]にによると 2019年以降 macFUSE はオープンソースではない:

Since 2019 (version 3.9) some components of the macFUSE software are no longer open source, e.g. the kernel extension. The source code of all open source components can be found in the release branch of the macfuse repository.

文献[2]は、この問題に対する macFUSE のメンテナンスを行っている Benjamin Fleischer のインタービュー記事である。企業の方はオープンソースの著者たちから大きな恩恵を受けながら、オープンソースを支えようとはしない。理由は単純で、無料だからであると...

Apple はオープンソースから多大の恩恵を受けている企業の一つである。Mac のコマンドの殆どは BSD 由来である。また開発で使われているコンパイラやツール類もまたオープンソースのコミュニティに由来している。現代社会は、オープンソースのコミュニティに属する人々の善意の無償のソフトウェアの上に成立しているのである。彼らに大学の教員などの生活基盤があれば問題は少ないが、しかしそのような生活基盤がない場合、誰がどのように彼らの生活を支えるのか?
ここで言う企業は、(多分) Apple を指していないだろう。文献[3]に Open Source に対する Apple の見解がある。Apple は Open Source にタダ乗りはしない。Apple も Open Source の発展に努力しているのだと。Google も Open Source を支えている[4]。そもそも macFUSE は Google の MacFUSE のコードを受け継いでいる。であるから macFUSE は誰が開発したのかを定めるのは実際には難しい問題である。他人の善意に甘え、ソースコードの出典すら隠す企業が多数存在するのだろう。
今回の問題は、企業と Open Source との関わりに一石を投じた。注意深く見守りたい。健全な姿は macFUSE がオープンソースであり続け、企業はオープンソースを支援する適切な関係を構築することである。しかしそれに必要な仕組みははっきりしない1


注 1. 他に
https://www.openssh.com
にも同様な苦情が書かれている

参考文献

[1] Open Source Status
https://github-wiki-see.page/m/macfuse/macfuse/wiki/Open-Source-Status

[2] FUSE for macOS: Why a popular open source library became closed source and commercially licensed
https://www.theregister.com/2019/12/16/fuse_macos_closed_source/
Mon 16 Dec 2019

[3] Open Source
https://developer.apple.com/opensource/

[4] Google Open Source
https://opensource.google

Apple M1

今日のパーソナルコンピュータには、スーパーコンピュータに使われていた技術が使われている。その代表的な例はパイプライン処理であろう。パイプライン処理は工場の生産ラインの流れ作業に例えることができる。ベルトコンベアに多数の作業員を並べ、製品を流していく。この方法は大量生産に向いているのだが、うまくいくためには一人ひとりの作業量が均一になるように調整されていなくてはならない1

ところが現在のパーソナルコンピュータの主流アーキテクチャであるインテル系の x86 の命令セットは複雑で、パイプライン処理に向いていない。そこで最近の x86 系の CPU では、命令をパイプライン処理に適した RISC 系の命令に変換してパイプラインに流しているらしい[1]。それなら率直に RISC 系の CPU を使った方が良いではないか?

最近(2020)、Apple は x86 に別れを告げ、自社設計した RISC 系の ARM/M1 を載せたパーソナルコンピュータを販売し出して好評を得ているらしい。Apple の M1 が成功すれば x86 に未来はないことになる。

他方では危惧していることもある。x86 の技術情報がオープンであり得たのは、Intel が第3者に彼らの CPU を使ってもらう必要があったからである。Apple の M1 はどうか? Mac だけでも M1 への投資を回収できるだけの需要があるだろう。その場合 Apple は技術情報の開示には消極的になるのではないだろうか? 現在だって macOS には隠された部分が多い。

記事[2]および同サイトに載っている関連した記事が面白い。

注 1: パイプライン処理を流れ作業に例えるのは単純化しすぎているかも知れない。流れ作業を実現するには CPU レベルでの並列動作を可能にしなければならない。それは(一般的に言えば)とてつもなく難しいと思う。文献[1]を読む限り、並列動作のやり方に工夫が凝らされている(p.328)。MIPS の場合、CPU への1つの命令は5つのステップ(命令フェッチ、命令デコード、実行、データアクセス、書き込み)に細分化され、細分化された同じ命令が同時に実行されないように工夫されている。

参考文献

[1] パターソン & ヘネシー:「コンピュータの構成と設計 —— ハードウェアとソフトウェアのインターフェース (第4版)」
(日経BP社,2011)

[2] RISCの実用性を証明した「MIPSアーキテクチャ」の誕生
https://www.itmedia.co.jp/news/articles/2004/23/news099.html

ZFS

2021/11/21

噂によると mac のファイルシステムに ZFS が利用できるようになるそうである。ZFS は既存の unix のファイルシステムに比べて優秀らしくて注目されている。snapshot を採る機能もある。Mac の Time Machin はファイルシステムの外にあったために、バックアップのときには外付けの HD と接続する必要があった。そのために僕のように面倒くさがり屋はついついバックアップをサボるのであった。ZFS の場合にはファイルシステムの仕組みとしてバックアップ機能を持っている。なかなか良いのではないだろうか? unix の世界も少しづつ Plan9 的な要素が取り入れられているんだね。残念ながら僕の MacBook は古くて、ZFS が載りそうにはない。

ZFS は現在の FreeBSD の標準ファイルシステムである。従って ZFS に関しては FreeBSD を調べれば解る。FreeBSD での知識は macOS においても活きるはずである。そこで以下では FreeBSD の ZFS の使い方を紹介する。

ここで取り上げる FreeBSD のバージョンは

bsd$ uname -a
FreeBSD bsd 13.0-RELEASE FreeBSD 13.0-RELEASE #0 releng/13.0-n244733-ea31abc261f: Fri Apr  9 04:24:09 UTC 2021     root@releng1.nyi.freebsd.org:/usr/obj/usr/src/amd64.amd64/sys/GENERIC  amd64
bsd$
また ZFS のバージョンは
bsd$ zfs version
zfs-2.0.0-FreeBSD_gf11b09dec
zfs-kmod-2.0.0-FreeBSD_gf11b09dec
bsd$
である。

FreeBSD が全面的に ZFS の下で動いていることは

bsd$ gpart show
=>       40  625140256  ada0  GPT  (298G)
         40       1024     1  freebsd-boot  (512K)
       1064        984        - free -  (492K)
       2048    4194304     2  freebsd-swap  (2.0G)
    4196352  620943360     3  freebsd-zfs  (296G)
  625139712        584        - free -  (292K)

bsd$
で確認できる。

ZFS ではデータの置き場を pool と言っている。pool の状態は

bsd$ zpool list
NAME    SIZE  ALLOC   FREE  CKPOINT  EXPANDSZ   FRAG    CAP  DEDUP    HEALTH  ALTROOT
zroot   296G  9.09G   287G        -         -     0%     3%  1.00x    ONLINE  -
bsd$
で判る。list によると pool は1つだけで、名前は zroot となっている。これは defualt の名前である。

さて df コマンドで mount の状態を調べると

bsd$ df
Filesystem         1K-blocks    Used     Avail Capacity  Mounted on
zroot/ROOT/default 298963956 2817280 296146676     1%    /
devfs                      1       1         0   100%    /dev
zroot/var/mail     296146856     180 296146676     0%    /var/mail
zroot/usr/src      296865524  718848 296146676     0%    /usr/src
zroot/var/tmp      296146772      96 296146676     0%    /var/tmp
zroot/var/audit    296146772      96 296146676     0%    /var/audit
zroot/var/crash    296146772      96 296146676     0%    /var/crash
zroot/usr/ports    296883972  737296 296146676     0%    /usr/ports
zroot/usr/home     296399220  252544 296146676     0%    /usr/home
zroot              296146772      96 296146676     0%    /zroot
zroot/tmp          296146840     164 296146676     0%    /tmp
zroot/var/log      296147252     576 296146676     0%    /var/log
bsd$
となっていた。ここに表示されたのは ZFS によって発生している mount である。mount コマンドも同じマウント関係を示す。なお "filesystem" とは、ZFS の内部。"mounted on" が我々に見える部分。

第1フィールドが解りにくい。何故こんなにたくさん表示されるのだろう? 3つ

zroot			/zroot
zroot/ROOT/default	/
devfs			/dev
あれば足りるのではないの?
と思うのだが、理由は推測するしかない。

類似の結果は次でも得られる

bsd$ zfs list
NAME                 USED  AVAIL     REFER  MOUNTPOINT
zroot               5.13G   282G       96K  /zroot
zroot/ROOT          3.04G   282G       96K  none
zroot/ROOT/default  3.04G   282G     3.04G  /
zroot/tmp            172K   282G      172K  /tmp
zroot/usr           2.09G   282G       96K  /usr
zroot/usr/home       576M   282G      576M  /usr/home
zroot/usr/ports      857M   282G      857M  /usr/ports
zroot/usr/src        702M   282G      702M  /usr/src
zroot/var           1.18M   282G       96K  /var
zroot/var/audit       96K   282G       96K  /var/audit
zroot/var/crash       96K   282G       96K  /var/crash
zroot/var/log        640K   282G      640K  /var/log
zroot/var/mail       188K   282G      188K  /var/mail
zroot/var/tmp         96K   282G       96K  /var/tmp
bsd$

この "NAME" 欄は、どのように機能しているのか?

ZFS の snapshot のマニュアル[1]を読むに、"NAME" 欄は snapshot と密接に関係している。例えば次のようにやる:

sudo zfs snapshot -r zroot/usr/home@2021-11-21
"@" の左は "NAME" 欄に存在しなくてはならない。"@" の右は snapshot を識別するための ID である。従って何でもよいが、重複は許されない。(撮影日にするのが良いだろう)
"-r" は再帰フラグで、これによって下位のディレクトリを含めた snapshot を採る。

snapshot のリストは

zfs list -t snapshot
で得られる。ファイルの内容は snapshot を採ったディレクトリの下にあり
bsd$ ls -l /home/.zfs/snapshot
total 1
drwxr-xr-x  4 root  wheel  4 Nov 20 11:46 2021-11-21
bsd$ ls -l /home/.zfs/snapshot/2021-11-21
...
bsd$
のように辿っていける。

従って、この "NAME" 欄は ZFS ご推奨の撮影スポットと考えられる。

"NAME" 欄は自由に再編できるが、下手すると後悔することになる。いろいろ弄って楽しみたいなら、潰してもよいように、VM 上の FreeBSD でやった方が無難である。

"rollback" とかがあるが、僕は副作用が怖くて運用中のシステムに試す気にはならない。

[1] zfs-snapshot(8)

clang v.s. gcc

2021/11/28

宣言か? それとも定義か?

#include <stdio.h>
int a;

void f(void);

int
main(int argc,char *argv[])
{
	f();
	printf("%d\n",a);
	return 0;
}

譜1: a.c

int a;

void
f(void)
{
	a += 1;
}

譜2: b.c

compile

cc -o a a.c b.c
OK on Ubuntu cc and Mac cc

However on FreeBSD cc

bsd$ cc -o a a.c b.c
ld: error: duplicate symbol: a
>>> defined at a.c
>>>            /tmp/a-4e27ce.o:(a)
>>> defined at b.c
>>>            /tmp/b-d70c4f.o:(.bss+0x0)
cc: error: linker command failed with exit code 1 (use -v to see invocation)
bsd$
つまり名前 a の定義が2ヶ所にあると言って跳ねられる。

"cc -v" が表示する cc のバージョンは

である。

問題は

int a;
が定義か、それとも宣言か? にある。どちらに解釈しても gcc も Mac の cc も筋が通らない。

僕のように古い人間は文献[1]で C を学んでいる。この本は(たぶん今でも) C プログラマにとってのバイブルである。以下これを C89 と言うことにする。実際には最初の版で C を学んだのであるが、ここでは言わないことにしよう。

文献[1]によると

int a;

int a=0;
を意味する定義である。従って clang が拒否するのは当然である。

Plan9 のコンパイラ(正しくはリンカ)はどうなっているか? やってみると gcc のように振る舞う。Plan9 プロジェクトにリッチーが関与しているにも関わらずである!

この問題についてネットで調べていると文献[2]に出会った。C99 では

int a;
の意味が違っているのだとか... これは
extern int a;
の意味だとか... 定義はあくまで a に明示的に値を与えなくてはならない。そこで
extern int a = 0;
も許され、定義なのだとか...

こんな旗色のはっきりしない仕様が C99 で「標準」になっているとは... 制定者たちの論理的センスを疑わざるを得ない。

なお C99 の正式な仕様書は有料でしか手に入らないらしい。文献[3]は draft であるが参考にはなるだろう。僕は規格書は無料にして欲しいと思う。ISO も日本規格協会も、規格書ビジネスは止めてほしい。オープンになっていない規格を強いる資格はだれにも無いはずである。我々はそのような「規格」に従う必要は無い。

[1] B.W.カーニハン/D.M.リッチー,石田晴久訳『プログラミング言語 C』
共立出版株式会社 (2006)

[2] C のグローバル変数の仮定義とは
https://gist.github.com/tenpoku1000/4900093cf2f87247cb523a3a7b808c82

[3] Committee Draft — Septermber 7, 2007 ISO/IEC 9899:TC3
http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1256.pdf

ポータブルなプログラムの書き方

カーニハン・リッチーは今でも C プログラマにとってのバイブルである。従ってこれを超えるのは慎重であるべきだ。いくら新しい「標準」があっても。

次のように書けばプログラマの意図が明瞭にコンパイラに伝わる。これは C89 のレベルである。もちろん gcc でも clang も正しくコンパイルされる。これを通さないコンパイラはバグっていると断じてよい。

a1.c

#include <stdio.h>
extern int a;
int a = 0;

void f(void);

int
main(int argc,char *argv[])
{
	f();
	printf("%d\n",a);
	return 0;
}

b1.c

extern int a;

void
f(void)
{
	a += 1;
}

なお a1.c

extern int a;
は無くてもよい。これをわざわざ入れたのは、このような宣言、すなわち複数のファイルに関係する共通の宣言は include 文によってヘッダファイルで指定する方が楽であり、有能なプログラマはそのようにプログラムを作るからである。ヘッダファイルの中には定義を書かないことが重要である。そのようなヘッダファイルを作ると gcc は通るが clang は通らない。

a1.c

int a = 0;

int a;
と書いても C89 では OK だが、C99 ではエラーになる可能性がある。大切なことはプログラマの意図をコンパイラに明瞭に伝えることである。

Brian W. Kernighan, Rob Pike

僕の観測によると、Mac の C コンパイラも gcc も甘い。作成した C のプログラムコードを FreeBSD の clang でも試してみると問題点が良く判る。