Design and Implementation of
Gracious Days

January 2017

先のライセンス話のどこがおかしいのか聞かれたのでまとめておく。

  • 「BSDライセンスはコードを改変した人の権利がまったく守られない」と書いているが、改変した部分の著作権は改変したひとに帰属する。BSDライセンス表示の頭にある著作権者表示(”Copyright”)の行に、自分の名前を追加することが多い。なので、ひとつのファイルがたくさんのひとに変更されると著作権者が増えるので、すべての名前を残さないといけない。件の記事にあるように「原著作者のものになってしまう」ようなことはない。著作権者は改変部分だけを切り出して、別のライセンスで再配布することもできる。

  • 「GPLでは、原著作者という考え方がなくなり、パブリックドメインに近くなる」という記述。これもデタラメ。著作権はBSDライセンスと同様、改変したひとに帰属するので、”Copyright” の行を増やすことができる。ただしGPLの場合、再配布に関して「自分の変更部分だけ GPL でない条件を適用したい」という条件設定を許さない。「架空の存在に著作権を受け渡す」なんて大きな嘘なので信じちゃだめ。

BSDライセンスは、使用と再配布において著作権表示とライセンス表示の保存を配布者と受領者に要求している。GPLは条項が複雑なので一文でまとめるのは難しいが、誤解を恐れずにまとめるなら、使用と再配布において著作権表示、無保証であることの言明、ライセンスの表示を保存することに加えて、原本・改変物の両方の配布において「GPLの全条文を配布内容全体に適用すること」を配布者と受領者に要求していて、かつその条文の再配布条件には「ソースコードが配布されていること」が含まれている。

どちらも著作権に関しては何もコントロールしていないが、ライセンスを決めることができるのは著作権者である。たとえば著作権者は、同じコードをBSDライセンスとGPLという、それぞれ異なるライセンスで同時に配布することができる。また、一度 GPL で配布してしばらくたった後、GPL での配布を中止して、BSDライセンスでの配布に切り替えることもできる。ただしどちらの場合も、一度配布され、自分の手を離れてしまった配布物のライセンスは変更できない。できるのは自分が配布するのを中止することだけだ。BSDライセンスに切り替わったとしても、受領者は GPL で配布されていた時の配布物を GPL で配布し続けることが可能だ。

改変者の著作権表示の追加や、異なるライセンスの共存の例

ライセンスを切り替えた例としては、SSH が有名だ。SSH はヘルシンキ工科大学に在籍していた Tatu Ylonen 氏が 1995 年に開発を開始し、次のライセンス条項でソースファイルを公開した。

Author: Tatu Ylonen <ylo@cs.hut.fi>
Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
                   All rights reserved

As far as I am concerned, the code I have written for this software
can be used freely for any purpose.  Any derived versions of this
software must be clearly marked as such, and if the derived work is
incompatible with the protocol description in the RFC file, it must be
called by a name other than "ssh" or "Secure Shell".

その後、彼は SSH Communications Security という会社を設立、SSH のソースコードの配布を止めてソフトウェア製品としての販売とサポート業務を開始した。その時点で著作権者は彼だけだったため、売り物にする目的でライセンスを変更したわけだ。しかし、ライセンスを変更してもそれまでに配布していたソースコードの配布を禁止することはできない。OpenBSD プロジェクトは、それ以前に配布されていたソースコードを集めて、そこから OpenSSH の開発を開始した。こちらはBSDライセンスになっているが、著作権表示とライセンス表示がどうなっているのか例をみてみよう。次は OpenBSDプロジェクトが配布しているssh.cの冒頭である。

Author: Tatu Ylonen <ylo@cs.hut.fi>
Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
                   All rights reserved
Ssh client program.  This program can be used to log into a remote machine.
The software supports strong authentication, encryption, and forwarding
of X11, TCP/IP, and authentication connections.

As far as I am concerned, the code I have written for this software
can be used freely for any purpose.  Any derived versions of this
software must be clearly marked as such, and if the derived work is
incompatible with the protocol description in the RFC file, it must be
called by a name other than "ssh" or "Secure Shell".

Copyright (c) 1999 Niels Provos.  All rights reserved.
Copyright (c) 2000, 2001, 2002, 2003 Markus Friedl.  All rights reserved.

Modified to work with SSL by Niels Provos <provos@citi.umich.edu>
in Canada (German citizen).

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
   notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
   notice, this list of conditions and the following disclaimer in the
   documentation and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

もともとの著作権表示とライセンス表示に追加される形で、2条項BSDライセンスと著作権者が記載されている。これは著作権者が複数いて、両方のライセンスが適用されることを意味している。それぞれのライセンスは矛盾する項目がないので共存できる。改変者の著作権や権利は、こういう形で守られるのである。元の著作権者の権利が剥奪されたり、改変者の著作権が認められなかったりすることは発生しない。

GPLとBSDライセンスは相互に矛盾する条項があるため、同時に適用することができない。したがって、GPLのソフトウェアを改変した場合、改変した部分や全体はGPLにしなければならない。ただしこの場合も、改変者の著作権は守られる。どちらのライセンスも、著作権の保持について何も制限していないからだ。

複数の著作権者がいる場合、全員が同意しないとライセンスを変更することができない。このことは、プロジェクト側で不都合が生じることがある。著作権者のリストが増えてくると管理が大変になることと、ライセンスの軽微な変更であっても著作権者全員の同意をとらなければならないからだ。ライセンスを変更することは滅多に発生しないのだが、その時になって誰かと連絡がとれなくなったり、著作権者がひとりでも自分の権利を主張すると困ったことになる。そのため、プロジェクトによってはコードの貢献に関して開発者とプロジェクトの間で著作権を譲渡させる契約を結んだり、一般に CLA (contributor license agreement) と呼ばれる、著作権の行使をプロジェクトに許諾するライセンス契約を結ぶ。たとえば前者は FSF (Free Software Foundation) が行なっていて(説明)、後者は ASF (Apache Software Foundation) が行なっている(個人向けCLAの契約書)。GPL も BSD ライセンスも、こういった契約なしに著作権が勝手に設定されるような乱暴な内容ではない。

GPL が適用されるソフトウェアの例

GPLの場合を見てみよう。次のライセンス表示は GNU troff のソースファイルにあるものだ。

Copyright (C) 1989-2000, 2001, 2002, 2003, 2004
   Free Software Foundation, Inc.
     Written by James Clark (jjc@jclark.com)

This file is part of groff.

groff is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free
Software Foundation; either version 2, or (at your option) any later
version.

groff is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
for more details.

You should have received a copy of the GNU General Public License along
with groff; see the file COPYING.  If not, write to the Free Software
Foundation, 51 Franklin St - Fifth Floor, Boston, MA 02110-1301, USA.

GNU troff は、コードの貢献者が著作権を FSF に譲渡しているため、”Copyright”行は FSF になる。その下にコードの著作者が書かれている。この個人名の表記は著作者人格権を尊重したもので、著作権者としての効力はない。その下には GPL が適用されることが書かれている。

次は a2ps というソフトウェアの例だ。

main.c -- main loop, and interface with user

Copyright (c) 1988-1993 Miguel Santana
Copyright (c) 1995-2000 Akim Demaille, Miguel Santana



This file is part of a2ps.

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; see the file COPYING.  If not, write to
the Free Software Foundation, 59 Temple Place - Suite 330,
Boston, MA 02111-1307, USA.

GNU troff と非常に似ているが、copyright 行が FSF になっておらず、コードの改変者が列挙されている。さらに改変した場合は、新たな著作権者として追加して問題はない。ここに列挙されているひとは、全員がライセンスを GPL とすることに同意したことになる。GPL でないライセンスで配布したければ、全員が同意してライセンスを変更するという手続きが必要だ。

まとめ

元記事は一体なにを参考に書いているのだろう? 「パブリックドメインを可能にしようとしたGPL」なんていう表現は、初めて目にしたように思う。どこかにそういう主張があるなら知りたい。

牧野武文著:ハッカーの系譜(9)オープンソースの巨人たち (11) 「伽藍とバザール」とネットスケープの決断より引用:

このようなオープンソースのライセンスには、BSDライセンスとGNUライセンスなどがある。BSDライセンスは、無保証であることと著作権表示を明記してあれば自由に再配布、改変できるというものだ。しかし、このライセンスだと、コードを改変した人の権利がまったく守られない。たとえば、コミュニケーターのバグを発見した人が、その部分のソースコードを修正してくれたとしても、再配布をするときは、ネットスケープ社に著作権がある旨を明記しなければならない。バグフィックスに貢献してくれた人の功績は、形式的には、すべて原著作者であるネットスケープ社のものになってしまう。二次配布をするときは、あくまでもネットスケープの著作物としておこなう。

では、GNUライセンス(GPL=GNU一般公衆利用許諾)ではどうか。GPLでは、原著作者という考え方がなくなり、パブリックドメインに近くなる。ただし、GPLが違うのは、再配布をするときも自動的にGPLにライセンスされるということだ。つまり、著作権をGPLという架空の存在に帰属させ、なおかつGPLから外れることを禁止することで、心ない人が勝手に権利主張することができないようにしている。

いまだにライセンスと著作権を混同して考えているひとは多いのだろうか。歴史ものは読んでいて面白いのだけれど、この連載には他にもいくつか気になる点がみつかる。ひとつはこれ。反論を書こうとしたものの、記事の指摘をすることを露骨に嫌がる職業ライターの方もいるようなので記録だけしておく。

オープンソースプロジェクトに参加したいな、と思った時、まず最初に問題だと感じるのは英語だと思う。構成員が日本人だけで、日本人に向けてのみ出しているそソフトウェアでない限り、プロジェクトの共通語はふつう英語だ。植山さんの記事には英語で物事を進めることの利点が体験談とともに書かれている。他の記事にも、オープンソースプロジェクトで上手いことやっていくためのひとつとして英語の話が出てくる。一方、英語のせいで参加したくても二の足を踏んでしまう、というのもよく聞く話だ。結論から言ってしまうと、やっぱり読み書きだけでも習得しないと話に入っていくのは難しい。ソフトウェア開発者の多くは多様性に対して寛容なので、英語が不得意という理由で拒絶されることはないだろう。ただ、特別な配慮もしてくれない。

しかし英語の前に、プロジェクトとの距離のとりかたを学ぶべきだと思う。いままでわたしが見てきたり、自分自身がやって良くなかったなと思う例を思い出しながら書いてみることにする。

プロジェクトへ参加したいひとへ

まず、メーリングリストや掲示板など、プロジェクトの開発者がコミュニケーションに使っているところに行こう。いまどきのプロジェクトはメーリングリストを使っていないかも知れないが、便宜上、以降では情報交換の場の代表としてメーリングリストという言葉を使うことにする。あなたはプロジェクトがどういうソフトウェアを作っているのかは、すでに知っているのだと思う。最初に学ぶべきは、「誰が」「何を」やっているのかということだ。プロジェクトは「ひと」で動いているので、自分の興味ある内容を実際にやっている開発者が誰なのかを知らないと、先に進めない。メーリングリストに参加して一カ月もすれば、「ああこの名前は良く見るなあ」というひとが見つかるはずだ。逆に、一カ月経過してもメールが全然流れなかったら、そのプロジェクトは活発な活動がなくなっているか、あなたが間違ったところを見ている。

誰が何をやっているのかが何となくわかったら、自分が何をしたいのかをもう一度考えよう。利用者として情報が欲しいだけなら、参加しているメーリングリストなどで時おり自分の質問を投稿したり、他人の質問にアドバイスしたりする活動が良い。「こう操作すると動作がおかしいんだけど」「こういう機能があると便利だと思うんだけど、何とかならないかな」というような投稿も、プロジェクトにとってプラスであることが多い。開発者が興味がないことは返事がないだろうし、興味があれば反応が来るだろう。返事が来るかどうか気になると思うけれど、あまり過度な期待はしないこと。あなたがプロジェクトに責任を持たなくて良いように、開発者もあなたの発言にいちいち反応しなければならない理由はないからだ。

利用者の立場からプロジェクトに参加しているひとの多くは、そういったコミュニケーションを続けている。コードも何も書いてないけれど、これは立派に参加していると言って良い。「何かを貢献しなきゃ」という意識は、あまり強く持たないことをお勧めする。気合いを入れれば入れるほど、それを受け入れてくれなかった時に落ち込むことになるからだ。自分がつくり出すものは、まず自分が有用だと思えるものでないと続かない。

利用者と開発者の区別は、コードを書くかどうかではなく、「自身の成果をプロジェクトに提供するひとかどうか」ということである。プロジェクトに参加する自らの姿勢の問題であり、実際に提供したものが受け入れられるかどうかは関係ない。プロジェクト側が区別したり、誰かが認めたりするものではないので注意していただきたい。

利用者として参加するときに求められること

端的には、コミュニケーション(メーリングリストを読むとか、掲示板を読むということ)に時間がかけられるかどうかが問われる。もちろん全部読む必要はなく、自分の興味のある部分だけで良い。そのような場所でのやりとりはプロジェクトにおける一次情報なので、まとまっていないしノイズも多い。リリースのときに情報が得られれば十分で、日常的にメールを読むなんて面倒だな、と思うひとは、参加しないほうが良いだろう。ここでひとつの判断基準になるのは、あなたがプロジェクトに興味があるのか、プロジェクトの生み出すソフトウェアに興味があるのか、という点だ。あなたが食事をしたいと思ってレストランに入った時、厨房でどうやって料理しているのか知りたいだろうか? プロジェクトのメーリングリストを読むというのは、そういう行為だ。最終的にできあがるソフトウェアだけに興味があるなら、出来上がるまでの過程を眺めてもしょうがない。

「料理をしているところを眺める気はないけど、食事はしたいからレストランは残ってて欲しい」という立場なら、プロジェクトに対して金銭的な支援ができないか調べよう。寄付を受け付けているところは多いし、もしそういう案内がなければ、開発者のいるところで率直に「寄付をしたいけどどうすれば良いの?」と聞いてみるのもひとつの方法だ。お金の他に、PCなどのハードウェアを寄贈するという方法もある。開発者の輪の中に入らず、もっと遠くからプロジェクトに貢献することは十分可能だ。

一方、「厨房で行なわれる料理を眺めていたい」というタイプのひとは、プロジェクトに興味があるひとだ。利用者の立場でプロジェクトを眺めていれば、説明文書やウェブページの整備という作業がプロジェクトの中ではなかなか進んでいないことに気が付くはずだ。ソフトウェア開発が中心のプロジェクトは、そういった部分に時間を多く割くことができない。「ここ情報が古いので、僕が更新しますよ」と言えば、歓迎してくれるだろう。同じ線上にある作業は、文書の翻訳だ。ただし、この手の作業は終りがなく、モチベーションを維持するのは難しいことをあらかじめ覚悟しておこう。

あなたは自分の作ったものを提出したり、自分の意見を表明することを躊躇するかも知れない。「こんな内容で良いのだろうか」「くだらない、品質が低いと言われたらショックだし、恥ずかしい」、そう思うかも知れない。こういう感情は、あなた以外もみんな持っている。自分のつくったものに対する意見を受け取るのは、誰だってある種の不安を感じることだ。自分で生み出したものには愛着があったり責任を感じるので、それを否定されることは自分や自分の能力が否定されるようで辛い。意識的に考えていなくても、共同社会で「何か独自のものを生み出して外に出す」という作業は辛いのだ。小説家、画家、音楽家、スポーツ選手や芸人といった職業は、常にこの種のストレスに晒されている。程度の差はあっても、人間であれば誰もが感じるストレスだ。どんなに優秀で経験を積んだプログラマであっても変わらない。

この辛さを乗り越えるには、価値基準を自分に置くことと、ひとつのものに拘らないことの 2 点が重要になる。自分のつくったものは、まず自分でその価値を認めてあげなければならない。誰に何と言われても、自分で役に立つと思うものをつくったなら、批判があっても深刻に受け入れる必要はない。また、「どうしてもこれが受け入れられないと嫌だ」というような拘りは、ある程度の範囲までにとどめよう。自分が貢献できる部分は貢献し、受け入れてもらえなかった部分は自分の中にとどめておけば良い。

自分自身が興味のあることをやって、その成果を提供しよう。「これをやればみんなの役に立てる」という発想で何かに取り組むのは、相当の覚悟がない限りは止めよう。続けるのが辛くなり、やがて取り組みに対する批判に耐えられなくなる時が来るからだ。仕事でもないのに、あなたが楽しめないことを続ける理由はない。逆に、あなたが極端に苦しむことなく文書などを成果として継続的に提供できるのであれば、開発者として貢献できる可能性が高い。

簡単にまとめると次のようになる。プロジェクトを理解し、ゴールを共有して参加するのは、開発者でなくても可能だ。

  • プロジェクトの開発者がコミュニケーションをとっている場に参加すること。メーリングリスト、IRC、電子掲示板などオンラインの情報交換に加えて、カンファレンスなどのイベントもある。
  • プロジェクトとの距離を意識すること。アナウンス用のメーリングリストだけを購読するのか、開発者の意見が飛び交うメーリングリストを読むのか、さらに自分でそこに積極的に投稿するのか、段階はさまざまある。あなたが興味があるのは何だろうか?
  • 質問する、質問に答える、意見するというアクションは、利用者として貢献できる方法のひとつだ。メーリングリストを読むのは面倒だけど貢献したいなら、寄付する、宣伝する(たとえばそのソフトウェアのインストール記事をあなたの blogに書く)という手もある。
  • 貢献を義務だと思う必要はない。自分のペースでやれることをやれば良い。

プロジェクトが求める人材とは

当然ながら、プロジェクトを動かしているのは「ひと」だ。あなたが開発者として参加したいと思った時にプロジェクトがあなたを歓迎するかどうかは、必ずしも「何らかの能力が秀でているかどうか」という基準で量られるわけではない。IEEE Spectrum 1999年10月号に、“How to be a Star Engineer”(PDF, 英語)という記事が載っている。IEEE はアイ・トリプル・イー(これが現在の正式な日本語表記である)と呼ばれる、米国に本拠地を置く電気工学・電子工学を中心とする技術者の組織である。Spectrumはその機関誌であり、さまざまな話題が載っている。その記事はカーネギーメロン大学のR.E.Kelleyという経営管理論の教授による「優れた技術者になるために必要なことは何だろうか」ということを書いたエッセイだ。日本の電子情報通信学会が 2001 年に翻訳版を「優秀なエンジニアになる方法」(PDF, 日本語)という題名で学会誌に載せている。面白い文章なので、一読をお勧めする。長いな、と思うひとは第1節と第2節までを読めば十分だ。

プロジェクトが求める人材は、第2節に出てくるヘンリとライの両方だ。プロジェクトは技術的な能力が高いひとが欲しい。しかしそれ以上に、みんなと仲良く同じゴールを目指してくれるひとを欲している。たとえばあなたが積極的にメーリングリストで初心者の相手をしてくれることは、プロジェクトにとって大きなプラスになる。プロジェクトやプロジェクトを中心とする開発者のコミュニティに利用者として参加することは、決して価値の低いことではない。あなた自身も、活動を通じてプロジェクトやソフトウェアについて学び、将来的により多様な貢献ができるかも知れない。

利用者としての関わりから、その後に開発者になった人もたくさんいる。すでに書いたとおり利用者と開発者は姿勢の問題なので、利用者が下で開発者が上、というような序列があるわけではない(一方で、このように信じているひとは多いは事実だが)。あなたがソフトウェア開発に興味があるのであれば、プロジェクトに利用者として参加しながら勉強するのもひとつの手だ。わたしは著名なプログラマがどういうコードを書いているのか、共同作業でどう意見交換をして物事を進めているのかにとても興味があった(今もある)。なので、特定の何人かの開発者の変更には目をとおして、そこからいろいろなテクニックを学んだ。本業では莫大な報酬を約束されるような優秀な開発者が書いているコードを無料で読めるというのは、素晴らしいことだと思う。

開発者として参加するときの距離感

コードを書いて貢献したいと思うひとは、利用者としてメーリングリストなどの情報交換の場に参加した後に、すでにいる開発者とコミュニケーションをとろう。コードを書いたら、開発者に伝えれば良い。メールで送るのが良いのか、Git の pull request にするのが良いのか、bugzilla にパッチを入れて PR を投稿すれば良いのか、プロジェクトによって風土が異なる部分が大きい。開発ツールの使い方がわからなければ、まずその勉強が必要だ。これは文書の貢献でも同じである。

自分の成果を提出する時には、「誰に伝えるのか」を意識すること。プロジェクトが小さければ主要な開発者に直接伝えるだけで十分だが、プロジェクトが大きい場合は、自分の変更に興味を持ったり、内容を理解してくれる開発者を絞りこんだほうが良い。メーリングリストを読んでいれば、誰が何に取り組んでいるのか学べるはずだ。それで足りなければ、そのプロジェクトが使っているバージョン管理システムやメーリングリストのアーカイブを使って、過去の変更者を調べよう。どうしてもわからなければ、メールなどで誰が詳しいのか聞いてしまうのが早いかも知れない。

誰が担当なのかを勉強せずに巨大な成果物を送りつけても、無視されるか受け入れるのに長い時間がかかることが多い。プロジェクトにはあなたのコードを受け入れる義務はないし、役所に書類を提出するのと違って、提出されたものが必ず処理されると期待できるものでもない。

どうすれば良いのだろうか。オープンソースプロジェクトは、多くの人が思うよりもっと人間的なものだ。かなり大きな組織であっても、重要な意思決定が飲み会の場で数人が話をして決まるようなことはよくあるし、いつも万人に公平な態度をとるわけではない。同じ目的を達成できるコードが 2 つあったとして、ひとつはいつも貢献している昔からいる開発者が作った少々品質の劣るコード、もうひとつは、どこの誰とも分からないひとから送られてきた品質の良いコードだったとしたら、どっちが採用されるだろうか。品質が良いコードがあるならそっちなのでは、と思うかも知れないが、前者が採用されることも良くある。

ここで重要なのは、将来的にこのコードの面倒を誰が見るのかという点である。プロジェクトの典型的な思考は次のようなものだ。昔からいる開発者であれば、おそらくすぐにはいなくならないだろう。どういう性格なのかも良く分かっている。コードの品質の悪いところは改善可能だ。まずいところを本人に伝えて、改善してから採用すれば良い。

一方、品質の良い、どこかから送られてきたコードは、開発したひととコミュニケーションがとれるかどうかで将来が決まる。送った開発者とメールのやりとりができるなら、「開発者として参加しませんか」と誘われるかも知れない。しかし、開発者と連絡がとれなかったり、最初は連絡がとれてもそのうち消息が分からなくなってしまうような印象があったとしたら、採用を躊躇してしまう。もちろん、すでにいる開発者が、その送られてきたコードの面倒をみることが無理なく可能だと判断すれば、採用するかも知れない。

この背後にあるのは、信頼できるかどうかという評価尺度である。あたりまえのことだが、初対面のひとより、いつもいるひとのほうが仲間意識が強い。共同体においてこれはとても重要だ。いつもいてプロジェクトのゴールを共有しているという姿勢を見せ続けるひとは信頼される。たとえば、メーリングリストでの質問に定期的に的確な答えを返しているひとがいたら、開発者の多くは名前を覚えるだろう。そしてそのひとが「コードを書いたんだけど」と成果物を出してきたら、おそらくみんな協力的になる。いつも初心者の相手という大変な作業をし続けている姿を見ているので、どうにか協力してあげたいな、という気持ちになるからだ。この気持ちの源泉は同じコミュニティにいるという仲間意識であり、プロジェクトを良くしていこうというゴールの共有である。そのひとが書くコードは拙く、採用されないかも知れない。しかし、もっと能力の高い開発者からのアドバイスは確実にもらえる。コードを書いた本人のコーディング能力も、活動を続けていればいずれ成長するだろう。

もちろん、当然ながら品質の良いコードを書けるひとは最初から信頼される。しかし、それと同じくらい「こいつは目的を共有した仲間なんだろうか」という村社会的意識があるのだ。つまり、開発者として参加するときのプロジェクトとの距離感というのは、開発者間のつきあいの深さ、目的共有の強さである。初対面という印象を拭える程度のつきあいがなければ、ふつうはまず開発者として認めてもらえない。社の主要事業も知らずに就職面接を受ける学生のようなものである。そして、開発者として参加してたくさんコードを書いたとしても、開発者とのコミュニケーションが欠けていると距離は縮まらない。

この距離のとり方は、自分の匙加減だ。わたしはプロジェクトとの距離を縮めて運営や意思決定というところにまで自分の責任範囲を広げたが、距離を保って、ある特定の部分のコードの開発を担当するという参加の方法もある。開発者としての参加の場合、利用者の場合と異なり「何を貢献するのか」がかなり明確なので、自分の負担にならない距離を保って活動を続けるのは難しくない。距離を縮めて損をすることはないので、コミュニケーションにはなるべく参加することをお勧めする。やりとりすることに嫌気がさしてきたら、たぶんあなたはもうそのプロジェクトへの興味を失っているのだと思う。無理して続ける必要はない。

あなたが、あなたにしかできない突出した技術を持つ優秀なプログラマなら話は別だ。そういった価値を持つ人材なら、プロジェクトは金を払ってでも、あなたをプロジェクトに留めようとするだろう。そうでないなら、開発者コミュニティに参加するということは、最終的に他の開発者とのつきあいを深めていくことだということを理解しよう。開発者コミュニティにはさまざまな形で参加しているひとがいて、コードだけで物事が決まるほど単純ではない。コードだけ提供してなるべく他の開発者に近付かずに関わり続けることも不可能ではないが、距離をとりたいならプロジェクトに参加する意味はないと思う。

「パッチを提供したいけど、後々の面倒はみたくないな」というひとは、面倒をみてくれる開発者を探して頼もう。バグ報告のシステムに登録しただけでは見てくれないかも知れないので、「ひと」とコミュニケーションすることが肝要だ。それさえ覚えておけば、自身が開発者としてプロジェクトに関わり続けなくても、コードで貢献することは可能だ。

プロジェクト運営の教訓

開発者コミュニティで開発者とのつきあいが長くなってきたら、プロジェクト運営の仕事をする機会が出てくる。リリースの作成作業であったり、セキュリティ問題への対応であったりと、特定領域で責任者として振舞う開発者である。形態はプロジェクトによってさまざまだ。FreeBSDは最初は複数の創設者が決定権を持っていて、次に意思決定機関であるコアチームをつくって合議制に移行した。コアチームのメンバは 9 名、任期は2年で、開発者の投票によって決まる。コアチームはプロジェクトの責任者にあたる開発者を指名し、個々の領域に責任を持つチームの編成を指示する。主なものにリリースエンジニアリングチーム、セキュリティオフィサ、ドキュメンテーションエンジニアリングチーム、portマネージャチームなどがある。

プロジェクトの意思決定の立場まで行くと、もうちょっとややこしい話が見えてくる。プロジェクト運営にトラブルはつきものだ。開発者間のケンカ、ルールを守らない開発者、メーリングリストなどで暴れる利用者などは分かりやすい例だが、高い能力を持つ開発者間でもプロジェクトの機能不全に陥るような問題が発生することがある。それらに対処しなければならない。

オープンソースプロジェクトの運営には、過去から蓄積された教訓がたくさんある。比較的最近の話をひとつとりあげると、2008年のGoogle I/O では、“How to Protect Your Open Source Project from Poisonous People”(動画)という有名な講演があった。これは Subversion プロジェクトの開発者であるBrian FitzpatrickとBen Collins-Sussman(もちろん両者とも googler だ)による体験談とオープンソースプロジェクト運営者への教訓話だ。動画は長く英語なので乱暴に要約すると、「プロジェクトに関わろうとするひとのうち、ゴールを理解・共有しないひと、自分で調べようとしないひと、現状の批判ばかりするひと、他人の意見を尊重しないひと、自分でできる内容を自分でやらないひとはプロジェクトに有害なひとなので、やんわり退場願おう」という話である。

この要素の中で一番重要なものをひとつあげるとしたら、すでに繰り返し強調しているように「プロジェクトの目的の共有」である。「BSDは亡びていいのに」という発言をするひとは、ゴールを共有していないので相手にされない(おそらく本人も相手をしてほしいとは考えていないと思うが)。OSSコミュニティの継続性で指摘した「利用者目線の意見に興味がない」という態度には、そうしないとプロジェクトにとって危険だということが背景にある。「BSDは存続すべきか」という議論はプロジェクトを徒に消耗させ、得られるものはほとんどない。

人間は、自分の興味のないものほど多様性の価値を低く見積もる傾向がある。わたしはアイドルグループにほとんど興味がないので、50人以上いるAKB48はもっと人数が少なくてもいいんじゃないかと思ったりする。近くの大型ショッピングセンターに行くと自転車売場があるが、たくさん種類が並んでいるのを見るとどうせ似たようなデザインなのだから数種類でいいのでは、とも思ったりする。一方、興味を持っているものにはこだわる。わたしは自分の使うマシンのキーボードに5576-A01を使っている。キーボードなんてどれも同じなんじゃないの、とは思わないし、一種類に統一されて欲しいとも思わない。Mac の市場シェアは Windows よりもだいぶ小さいけれど、わたしは Mac のほうが好みなので使い続けている。

化粧品、時計、服のブランド、なんでも良い。あなたの日常生活でどれでも良いと思っているものと、こだわっているものの違いは何だろうか。そしてあなたは、自分が「どうでも良い」と思っていることに独自の考えを持っているひとと議論ができるだろうか。あなたは他人からこだわりを力説されても、心変わりしたりはしないだろう。ボランティア開発者が行なっているソフトウェア開発プロジェクトは、そのひとが興味があるからやっているのである。興味を持たないひととの価値観の議論は、そもそも噛み合わない。

合わないひととは関わらない、という簡単な話である。「FreeBSDに関係しているひとはへんなひとばかりだ」「そんな考えだからFreeBSDはダメになったんだ」と思うのも、発言するのも自由だと思う。ただ、わたしも含めてプロジェクトの人間はそういう議論はしないし、発言者を説得することもない。価値観は多様性があるものだし、プロジェクトが求めているのは目的を共有してくれるひとだけだ。プロジェクトを守るためには、そういうひとたちの相手はしないのが正しい。プロジェクトに近付いてきて実害が出るようなことがあれば、露骨ではないにしろ何らかの形で排除しようとするだろう。FreeBSDプロジェクトが目指しているのは、みんなを説得してFreeBSDを使わせることではなく、FreeBSDの品質を向上させることだ。そして品質の向上という目的を共有する開発者・利用者の集団を醸成することである。

この講演では、他にも健全な開発者コミュニティを維持するためにプロジェクトが気をつけるべき内容が説明されている。次のようなものだ。

  • 完璧を求める開発者、自分の作業領域から他人を排除しようとする開発者は、プロジェクトの開発を麻痺させるので注意しよう。

  • 目標を明確に、議論は論点を明確にする。同じ議論は繰り返さず、すべてのメールに逐一返信するような会話は避ける。

  • 議論や意思決定の記録をとり、共有する。

  • 開発者間でコミュニケーションを密にとる。コードの変更は互いにレビューする。

  • 開発の手順を決め、文書化する

  • 投票は最後の手段

このような教訓を実践しなければならない立場になることは少ないと思うが、開発者として大きなプロジェクトに関わるときには、多かれ少なかれ開発者同士の意見の衝突を経験することになるだろう。まともなプロジェクト運営者であれば、上述のような開発者コミュニティの外から来る攻撃からプロジェクトを守る努力をしてくれるはずだ。しかし、内部の開発者同士の意見の衝突は自分で何とかしなければならない。そのときに相談できる相手は、やっぱり同じプロジェクトの開発者だ。実生活にいる友人と同じである。普段から仲良くしている相手なら、トラブルの相談も気軽にできるだろう。コードだけのやりとりでは貴重な人脈形成の機会を失ってしまうので、とてももったいない。

仲良きことは…

オープンソースプロジェクトのメーリングリストを眺めていると、「なんでみんなギスギスしてるんだろう」と思うひともいるかも知れない。拙い質問や品質の低いコードにはマサカリが飛びまくる。遠慮がなく口が悪い開発者がいるプロジェクトも、それなりにある。たとえば2013年にLKMLであった Sarah Sharp による Linus の罵詈雑言批判の一幕が江添さんのサイトにまとめられている。彼女は Linux カーネルの xHCI ドライバのメンテナだったが、その後Linuxカーネルの開発者コミュニティを去った。

ここ数年、さまざまなプロジェクトでコミュニティ内のハラスメントや感情的な衝突が問題になり、行動規範 (Code of Conduct) を定めたところが多い。FreeBSDのCoCもウェブサイトに載せてある。これだけで万事解決とは言わないが、コミュニティの構成員が不快な思いをしないですむような努力は継続的に行なわれている。感情的な衝突が発生したら、双方の意見を聞いたり電話で直接本人と話すこともよくある。技術的正当性をふりかざして正論をぶつけるだけでは、ひとのコミュニティはうまく回らない。プロジェクトの外からはメールでのやりとりしか見えないことが多いが、実際のところ運営側でやっていることはもうちょっと複雑だ。Linuxカーネル開発者のコミュニティは大きく、企業ユーザの政治力学が働くなどオープンソースプロジェクトの中では少々特殊な部類に入るので、残念ながら罵詈雑言が消えることはないのかも知れない。

オープンソースプロジェクトに興味のあるひとは、自分の居心地のよいプロジェクトを探してみよう。大学のサークル活動のようなものだと思っていて良い。そこでの活動は、他では得難い経験や知的な刺激が得られる貴重なものになるはずだが、何の集まりなのかを理解せずに入ってもうまく行かない。ソフトウェアそのものだけではなく、プロジェクトの目的を共有するよう心がけることを強くお勧めする。

昨年の11/29に勉強会で使ったZFSの性能測定とチューニングの資料のリンクを、Twitterに投げただけになっていたので、参照しやすいようにここにも貼っておく。しゃべり資料は後から読んでもわかりにくいので、後ほどもうちょっと整理して文書化する予定。

ZFSはスケールするファイルシステムだけれど、速いファイルシステムではないので性能の話を見聞きする時には疑ってかかるのが吉。

このあたりで質問した内容。修正がコミットされて解決した。

Userland DTrace のコンパイル時の処理

Userland DTrace では、USDT provider(ユーザレベル静的定義トレースプロバイダ)を定義する際に、プローブをC言語のヘッダファイルとして生成する機能がある。プローブ定義はD言語で次のような書式を使って記述する。

provider sample {
    probe first__read(int, char *);
    probe debuglog(char *);
};

これをfoo.dというファイルに置いてdtrace -h -s foo.dとすると、次のようなマクロ定義が含まれるfoo.hというファイルができる。

#define SAMPLE_FIRST_READ(arg0, arg1) \
    __dtrace_sample___first__read(arg0, arg1)
#define SAMPLE_DEBUGLOG(arg0) \
    __dtrace_sample___debuglog(arg0)

プログラムではこのfoo.hをインクルードして、プローブを置きたいところにマクロを記述する。関数呼出だが、DTraceを有効にしなければnop命令に書き換えられるので、性能への影響は小さい。

これらの関数は当然ながら対象プログラムに含まれていない。そのため、対象プログラムのオブジェクトファイル(ここではsample.oとする)ができた後に、次のように-Gオプションをつけてdtraceコマンドを実行することで生成する。

% dtrace -G -o foo.o -s foo.d sample.o

foo.oには、foo.dで定義されたシンボルの実体が入っている。したがって、最後にすべてをリンクすればUserland DTraceに対応したバイナリができあがる。

シンボル置換

foo.dで定義したfirst_readというプローブは名前に二重下線が含まれているが、実際のプローブ名はfirst-readのようにハイフンに置き換えられる。この処理は、dtrace -Gコマンドがオブジェクトファイルの中のシンボルテーブルを直接書き換えて実現している。

% nm sample.o
            U __dtrace_sample___first__read
            U __stack_chk_fail
            U __stack_chk_guard
0000000000000000    T main
            U read

% dtrace -C -x nolibs -G -o foo.o -s foo.d sample.o

% nm sample.o
            U __dtrace_sample___first-read
            U __stack_chk_fail
            U __stack_chk_guard
0000000000000000    T main
            U ead

プログラムのオブジェクトファイルにある最初の__dtrace_sample__first__readというシンボルが、dtrace -Gコマンドの後に置き換わっているのが分かる。

DTraceのプローブを定義しながら開発を進めていたところ、時々プログラムのリンク時にエラーが出るようになった。先ほどの実行例を良くみると、置き換え後の read のシンボルの頭が 1 文字欠けているのが分かると思う。プローブの名前にも read があるので、これは置き換えの処理がおかしいのでは、というところまであたりをつけたところで、冒頭の質問メールを出してみた。

するとIllumos-gate のバグ報告6653で報告されているものと同一の症状のようだ。原因は次のとおりである。

  • シンボルテーブルはテーブルのオフセットが記録されているが、個々のシンボルがテーブル内でユニークな領域を占める保証はない。つまり"first-read" というシンボルと "read" というシンボルは、同じ部分文字列を含む可能性がある。

ELFのシンボルテーブルを生成するツールによっては文字列の重複部分を最適化するようだ。__ を長さの違う - に置換するため、オフセットがずれてreadの頭一文字が欠けてしまう。レポートには binutils の gas 2.26 以降で発生するとある。dtrace -Gの前にobjcopyを実行してsample.oをコピーすると、症状が出なかった。

そもそもシンボルテーブルを書き換えるのではなくて、シンボルのルックアップ時に__-に置き換えるべきなのでは、というもっともな指摘もあり、FreeBSDにはこういう修正がcommitされた。FreeBSD 10 系はそもそも問題が発生するシンボルテーブルを生成しないので影響はない。

Userland DTraceは使っているひとが少ないのか、不具合がまだまだあるようだ。

シェルスクリプトの記事の動作チェックをしていた時に、BSDのuniq(1)の出力がPOSIXに準拠していないことに気がついた。

% jot -b 1 100 | uniq -c
 100 1

SUSv3のOUTPUT FILESの節には、次のように書いてある。

If the -c option is specified, the output file shall be empty or each line shall be of the form:

"%d %s", <number of duplicates>, <line>

BSDのコードは昔から"%4d %s"だ。この書式指定は少なくとも1980年まで遡ることができる。ずっと変わっていない。確認してみたところ Solarisの実装"%4d" だ。SCO UNIX はソースが確認できないが動作を調べると"%4d"っぽい。

古いUNIXのソースを遡ってみると4th Editionまで遡ることができた。1975年5月のソースだが、これも"%4d" だ。

一方GNU coreutils (確認したバージョンはuniq 8.4)は次のように表示する。

    100 1

ソースファイルを確認する限りでは"%7" PRIuMAXのようだ。Gitの履歴からたどれるもっとも古い1992年11月のバージョンでは"%7d"になっている。ということは、POSIXを信じてuniq -c | cut -f 1 -d " "とかすると痛い目をみる、ということだろうか。

uniq -cの出力が%dになっている環境を知っている方、情報求む。

このあたりで質問した内容。原因は判明したが、まだ未解決。

DTrace における if-then 文

DTraceのD言語にはif-thenも含めて制御構文がほとんどない。これは無限ループなどを発生させないために言語仕様(厳密にはD言語がコンパイルされた後のバイトコードの仕様)で禁じているためだ。なので、たとえば switch 文などのような条件分岐は、次のように二項演算子で書く必要がある。

inline string afstr[int af] =
    af == AF_UNSPEC ? "AF_UNSPEC" :
    af == AF_LOCAL ? "AF_LOCAL" :
    af == AF_INET ? "AF_INET" :
    af == AF_INET6 ? "AF_INET6" :
    "<unknown>";

DTraceの開発の中心となっているIllumos-gateで、昨年の8月にif-then構文が追加された。FreeBSDでは11から、この変更が含まれている。

if-thenといっても、バイトコードに条件ジャンプ命令が追加されたわけではなく、プローブを自動分割することで疑似的に if-then の機能を実現する構文糖衣である。

:::probe
{
    printf("ENTRY\n");
    if (arg0 == 0) {
        printf("FOO\n");
    } else if {arg1 == 1 && arg2 == 1) {
        printf("BAR\n");
    } else {
        printf("BAZ\n");
    }
    printf("LEAVE\n");
}

上記のようなスクリプトは、下記のスクリプトと等価だ。

:::probe
{
    printf("ENTRY\n");
}
:::probe /arg0 == 0/
{
    printf("FOO\n");
}
:::probe /arg1 == 1 && arg2 == 1/
{
    printf("BAR\n");
}
:::probe /arg0 != 0 && !(arg1 == 1 && arg2 == 1)/
{
    printf("BAZ\n");
}
:::probe
{
    printf("LEAVE\n");
}

if-thenは述語の異なるアクション定義の並びになる。同一のプローブに対する複数のアクション定義は、上から順番に実行されることが保証されている。

メールに出した質問

if-then 構文を使って、とある PID プローブに対して次のように書いた。

pid$target:::probe
{
    this->st = copyin(arg0, 1024);

    if (arg1 == 1) {
        printf("st = %s\n", stringof(this->st));
    } else {
        printf("unknown\n");
    }
}

arg0を1024バイト分抽出して、それを処理するコードである。上記コードは説明を簡単にするため、arg0全体をヌル終端の文字列とみなしてprintf()しているが、実際はarg0が全体の大きさが1024バイトの構造体を指していて、その一部が文字配列になっているので、それを表示したい、という意図のコードの一部だった。

しかしこれを実行すると、arg1 == 1の時に文字列が表示されない。おかしいと思って次のように書き換えてみると、こちらはちゃんと動く。

pid$target:::probe
{
    this->st = copyinstr(arg0);

    if (arg1 == 1) {
        printf("st = %s\n", this->st);
    } else {
        printf("unknown\n");
    }
}

どうにも原因が分からず、冒頭の質問メールで聞いてみたところ、次の事実が分かった。

  • 同一プローブに対するアクション定義はすべて順番に実行されるが、ローカル(clause-local)変数への代入のうち copyin() でカーネルにコピーしたデータは、アクション定義の境界以降、アクセスできなくなる。
  • copyinstr()でカーネルにコピーしたデータは、アクション定義の境界を跨ってもアクセスできる。
  • copyin(), copyinstr()のいずれにも無関係に定義されたデータは、アクション定義の境界を跨ってもアクセスできる。

現在の実装では、if (0) { } のような無意味な if 文を挟んだだけで、copyin() した変数の内容へアクセスできなくなってしまう。arg0 が壊れるわけではないのでもう一度 copyin() すればデータは取得できる。DTraceのマニュアルには、「同一プローブに対するアクション定義は結合される」とあるので、ローカル変数へのアクセスができなくなるというのはバグと呼んで良いと思う。

ローカル変数へのストア・ロードは stlsldls というバイトコードで行なわれる。アクション定義境界で、一時バッファにロードしたデータへのアクセスができなくなってしまうようだ。copyin()copyinstr()では処理が異なっており、後者は一時バッファとは異なる領域に保存されるため、たまたま継続してアクセスができるようになっている。いずれにしても、「アクション定義を分割して書く」ことは、最初は想定していなかったのかも知れない。

現存するすべてのDTrace実装が同じ問題を抱えている。どうやって解決するかは議論中だが、現段階では複数に分割されたアクション定義でcopyin()したデータを使う場合には、プログラムする側が注意するしかなさそうだ。

昨年の夏からずっとこの問題に悩まされていて、Appleがいまだに修正してくれないので状況をまとめてみた。

macOS を NFS サーバとして使う

VMWare や VirtualBox を使い仮想マシン上で別の OS を動かしている場合、気になるのはストレージの消費である。わたしは主に開発用途で各種 Unix系OSを扱うことが多いが、GitやSubversionでソースファイルを展開し、試作用ブランチも含めて 10-20 GB くらいはすぐに埋まってしまう。

そこで、ホストの macOS を NFS サーバとして、複数の仮想マシンからホームディレクトリを NFS マウントすることで共有する使い方をしている。設定は簡単だ。たとえば/etc/exports に次のように書いて、nfsd enable && nfsd startとすれば良い。仮想マシンが接続される内部ネットワークが完全に管理でき、使用者しかログインしない個人のPC上であれば、export範囲をそのネットワークに限定するだけでセキュリティ対策は十分だろう。

/Users/hrs  -maproot=0:0 -network 172.16.0.0 -mask 255.255.0.0

あとは、仮想マシン上のOSから、ホストOSのIPアドレスにアクセスしてNFSマウントすれば良い。

問題

Mac OS X が macOS に名前を変えた 10.12 以降、上記のような使い方をしていたらマシンが落ちるようになった。クラッシュダンプをみると、いつもnfsdのプロセスの実行コンテキストで落ちている。

Anonymous UUID:       A830AD5D-A7AA-1116-BC69-8E6F8FF79559

Tue Dec  6 05:09:46 2016

*** Panic Report ***
panic(cpu 2 caller 0xffffff800ba05ead): Kernel trap at 0xffffff800bb0821f, type 14=page fault, registers:
CR0: 0x000000008001003b, CR2: 0x0000000100000074, CR3: 0x0000000454779065, CR4: 0x00000000003627e0
RAX: 0xffffff803ddd9f00, RBX: 0xffffff803ecb1201, RCX: 0x000000000000a504, RDX: 0xffffff802b5b89b0
RSP: 0xffffff91fa5c3610, RBP: 0xffffff91fa5c3680, RSI: 0xffffff802b5b89b0, RDI: 0x0000000000000000
R8:  0x0000000100000000, R9:  0x000000000000a504, R10: 0x0000000000000006, R11: 0xffffff803ac01a28
R12: 0xffffff91fa5c3c88, R13: 0x0000000000000000, R14: 0x0000000000000000, R15: 0x0000000000000000
RFL: 0x0000000000010206, RIP: 0xffffff800bb0821f, CS:  0x0000000000000008, SS:  0x0000000000000010
Fault CR2: 0x0000000100000074, Error code: 0x0000000000000000, Fault CPU: 0x2, PL: 0, VF: 1

Backtrace (CPU 2), Frame : Return Address
0xffffff91fa5c32a0 : 0xffffff800b8f211c 
0xffffff91fa5c3320 : 0xffffff800ba05ead 
0xffffff91fa5c3500 : 0xffffff800b8a3743 
0xffffff91fa5c3520 : 0xffffff800bb0821f 
0xffffff91fa5c3680 : 0xffffff800bb159cf 
0xffffff91fa5c3750 : 0xffffff800baacbad 
0xffffff91fa5c37b0 : 0xffffff800ba80fb0 
0xffffff91fa5c3e50 : 0xffffff800bab2743 
0xffffff91fa5c3ef0 : 0xffffff800bab15a8 
0xffffff91fa5c3f50 : 0xffffff800be273ca 
0xffffff91fa5c3fb0 : 0xffffff800b8a3f46 

BSD process name corresponding to current thread: nfsd

Mac OS version:
16C60b

Kernel version:
Darwin Kernel Version 16.3.0: Tue Nov 29 12:39:07 PST 2016; root:xnu-3789.31.2~11/RELEASE_X86_64

NFSが原因であることが分かったため、アクセスパターンに依存性があるかどうかを調べたところ、どうも深いディレクトリをrm -rで削除するタイミングで必ず落ちるようだ。しかし、macOSをNFSクライアントにして、127.0.0.1でループバックマウントして同じ操作をしてみても落ちない。

パケットダンプをして調べてみると、次のタイミングで落ちることが分かった。192.168.0.10が外部のNFSクライアント、192.168.0.122がmacOSである。

03:17:22.388639 IP (tos 0x0, ttl 64, id 22647, offset 0, flags [DF], proto TCP (6), length 288)
    192.168.0.122.2049 > 192.168.0.10.1033309124: reply ok 232 lookup fh Unknown/4E5800005885F95A0000000100000008002B4BBF002B4BBF0000000100000002 DIR 755 ids 0/20001 sz 68 nlink 2 rdev 0/0 fsid 1000002 fileid 2b4bbf a/m/ctime 1481048229.000000 1474554809.000000 1478175928.000000 post dattr: DIR 755 ids 0/20001 sz 102 nlink 3 rdev 0/0 fsid 1000002 fileid 2b4bbe a/m/ctime 1481048229.000000 1474554809.000000 1478175928.000000
03:17:22.388653 IP (tos 0x0, ttl 64, id 3392, offset 0, flags [DF], proto TCP (6), length 172)
    192.168.0.10.1033309125 > 192.168.0.122.2049: 116 rmdir fh Unknown/4E5800005885F95A0000000100000008002B4BBE002B4BBE0000000137000000 "7"
03:17:22.388912 IP (tos 0x0, ttl 64, id 58314, offset 0, flags [DF], proto TCP (6), length 52)
    192.168.0.122.2049 > 192.168.0.10.978: Flags [.], cksum 0x4cb0 (correct), seq 20705, ack 14493, win 4092, options [nop,nop,TS val 527924047 ecr 2310910003], length 0
03:17:22.392801 IP (tos 0x0, ttl 64, id 2870, offset 0, flags [DF], proto TCP (6), length 200)
    192.168.0.122.2049 > 192.168.0.10.1033309125: reply ok 144 rmdir PRE: sz 102 mtime 1474554809.000000 ctime 1478175928.000000 POST: DIR 755 ids 0/20001 sz 68 nlink 2 rdev 0/0 fsid 1000002 fileid 2b4bbe a/m/ctime 1481048229.000000 1481048242.000000 1481048242.000000
03:17:22.392870 IP (tos 0x0, ttl 64, id 3393, offset 0, flags [DF], proto TCP (6), length 172)
    192.168.0.10.1033309126 > 192.168.0.122.2049: 116 lookup fh Unknown/4E5800005885F95A0000000100000008002B4BBE002B4BBE000000022E2E0000 ".."

このダンプは、mkdir -p 1/2/3/4/5/6/7; rm -rf 1 というコマンドを実行した結果である。NFS RMDIR "7"の後、NFS LOOKUP ".."が発行されているのが分かる。落ちるのは、このNFS LOOKUPのRPCを受け取った瞬間だ。ディレクトリの削除でなくても、NFS LOOKUP ".."が発生するアクセスを行なうと、簡単に再現できる。

macOS のNFSクライアントの場合、rmdir で再帰的にディレクトリを削除する時に".."を使わないので落ちない。上位ディレクトリのエントリはキャッシュに入っている値を使うようだ。キャッシュの影響が出ないようなアクセスでcd ..を実行したら、macOSのNFSクライアントでも落すことができた。当初はFreeBSDのNFSクライアントに依存する問題かと思ったが、原因がこの RPC にあるのは確実だろう。

ちなみに検証にはFreeBSDの他、nfsshellを使った。

影響

NFS LOOKUPのRPC一発でOSが落ちてしまうというのは、とてもフラストレーションが溜る状況であるとともに、リモートDoSの危険性がある。幸い、正しいファイルハンドルでなければ受理しないようなので無差別に落せるわけではない。NFSサーバを有効にしないように注意することをお勧めする。

影響する範囲は 10.12 以降のすべて(本エントリの執筆時点では 10.12.3 beta まで確認)である。それ以前では再現しない。バグレポートは送っているものの半年近く修正されないままなので、ちゃんと読まれていないのかも知れない。

力武さんがこういう記事を書いている。自分自身BSD界隈に関わって15年は経つし、コミュニティ運営についてはいろいろと経験を積んだつもりなので、意見を書いてみることにした。書いてみたらまとまりのない文章になってしまったけれど、残しておくことにする。

目的のないコミュニティは続かない

「コミュニティに参加している人の年齢が高くなって、そのうちなくなるんじゃないか」という懸念は、少なくともFreeBSDには当てはまらない。活動がなくなる開発者もいるが、若いcommitter(ソースコードの変更権限を持っている開発者)は定期的に入ってきていて、人材の流動性は入口も出口も高い。CSRGの時代に活躍していた人が最近になって開発に再参加するケースも増えた。これらは国際会議の運営などを通じて交流を深める努力をしている成果でもある。50代や60代でコードを書いて参加しているひとはたくさんいて、特別なことではない。日本では忘れられて行っているような印象があるけれど、縮小したり自然消滅してしまうような状況ではないことは自信を持って言える。

一方、日本のFreeBSDユーザコミュニティは2000年代前半からすでに機能不全に陥っていたと思う。みんな情報交換のために集まっていて、興味を持つひとが少なくなるにつれてコミュニティとして機能しなくなった。

前者は開発者のコミュニティであり、後者は利用者のコミュニティである。利用者のコミュニティは、興味を持つひとの人数が維持されなければ続かなくて当然だし、続けなければならないものでもないと思う。多くの人は自分の欲しい情報が得られればそれで良く、集団で何かを作り上げようという意識はない。もちろん文書の作成やメーリングリストの運営、交流会の企画などの情報交換を促進する努力が行なわれているコミュニティはあるし、日本のFreeBSDのユーザグループもかつてはそうだった。ユーザグループが消滅したのは、みんなFreeBSDを使わなくなって、そういう情報交換が必要なくなったからだ。FreeBSDに限らず、BSD由来のコードを題材とするコミュニティは地域ユーザグループなどの形で日本にはまだ残っているが、純粋に利用者だけのグループはほとんど消えたと思う。

これを、構成員が高齢になったとか忙しくなったとか考えるのは、適切な分析ではないと思う。開発者コミュニティには、高齢なひとも忙しいひともいる例が多々あるからだ。そしてみんな開発するという目的を共有している。それに対して利用者コミュニティは集団としての目的意識が希薄で、本質的に性質を異にするコミュニティなのである。後者は、自分から何かを提供しようという方向ではなく、何かを得ようという方向で動機づけされた集団だという点が違う。だから当然、流行っている時はひとが多く、トレンドから外れると少なくなる。飽きたか、必要なくなっただけだ。

もうひとつ補足すると、日本にはFreeBSDの開発者コミュニティもあった。最も大きかったのはPAOだ。いわゆるnewconfig vs newbus論争に敗れたことや、ACPI CAのimportなどによって日本のコミュニティの開発成果の大部分が受理されないことに憤ったひとたちは、愛想を尽かせていなくなったりNetBSDプロジェクトに移った。つまり開発者のコミュニティは明確な理由があって消滅している。開発者でない、使うだけのユーザはしばらくコミュニティに残っていたが、FreeBSDの利用数の減少にともなって姿を消していった。

ちなみに、現時点でFreeBSDのcommitter, 特にソースコードを変更する権限を持って活動している日本人は存在するが、数はとても少ない(FreeBSDはsrc, ports, doc の 3 種類のcommit権限が分かれており、変更権限を得るにはそれぞれで実績を示す必要がある)。もう某オーストラリアの人とかは残っておらずプロジェクトは風通しのよい組織になったけれど、日本のローカルな開発者コミュニティの状況には変化がない。ソースコードの変更権限を持っていて、かつきちんと知識を持っているひとは数人だ。

開発者のコミュニティ

前述のとおり、OSSのコミュニティは構成員が利用者である場合と開発者である場合とで、性質が違う。これらを同一線上で考えることはできないので、まずはそれを分けて考えるべきだ。もちろんOSSプロジェクトは両方を含むコミュニティを形成することが多いので、「ほとんど利用者のみ」「開発者+利用者」の2種類と考えたほうが実際に近いかも知れない。日本のOSSコミュニティは、大半が利用者のコミュニティだ。開発者が参加していることもあるが、開発の議論が行なわれている現場は少ない。

日経コンピュータがゾンビOSSと表現した記事を2014年に出している。ここで根底にあるOSSに対する考え方は「無料で使えるソフトウェア資産」なのだと思う。それを支える開発者コミュニティへの認知が欠落していて、そこには有用性を見出していないようだ。「有用なOSSの情報を共有したい」「使い方を学びたい」。利用者のコミュニティの発生につながっているのは、そういった動機である。したがって、大部分の興味が別のソフトウェアに移ってしまえば、こういう利用者のコミュニティは自然消滅する。特定のOSSを名指しして、あたかも存在が害であるかのように連想させる「ゾンビ」と表現していることは失礼極まりないが、なぜそういう発想にたどり着くのかは理解できる。単に、利用可能な資産を評価するという立場でモノを言っているだけだ。そして、利用者側の視点から「いつまで開発が続くのだろうか」ということを不安に感じるのも自然だ。

英語を共通語としていることが多いからなのか、日本では開発者のコミュニティに直接参画しようという動きは少ないように思う。また、「あるOSSプロジェクトを中心にゆるく連帯した開発者の集団が継続的に存在していること」は、市場における人材育成の点で非常の大きな効果があるという点は、あまり認識されていないように感じる。

開発に参画しているひとの多くは本職を持っていて、それがOSSと関連していることがしばしばある。共同での開発によって組織間の技術交流が自然と発生するため、プロジェクトの規模が大きくなってくると成果物としてのソフトウェアが得られるとともに、それに精通した開発者が得られる。ソフトウェア技術者の人材流動性が非常に高い米国のような場所では、OSSプロジェクトを開発者資源として活用することで成果をあげている例が多い。特にうまくOSSを活用しているのはAppleだ。彼らは社内のビジネスに必要なソフトウェアを、OSSプロジェクトを支援することで得るという手法に長けている。現在のmacOSの基盤になったRhapsodyが公開されたのは1997年のWWDCだった。Mach 3とFreeBSD 5系のコードを組み合わせてモノリシックカーネルにしたもので、ユーザランドのコンポーネントにもOSSが多く使われていた。現在もツールチェインとしてLLVM/Clangを使う、認証基盤としてHeimdal Kerberosを取り込むなど、開発に非常に大きな労力がかかる部分にOSSプロジェクトの成果を使っている。また、彼らは単にコードを使うだけでなく、開発者コミュニティにいる開発者を雇用したり金銭的な援助を行なったりして、コミュニティを育てる努力を怠っていない。OSSプロジェクトが持つソフトウェア資産だけでなく、開発者を資産として認識しているからだ。差別化が必要な部分は社内で開発し、重要だが開発者コミュニティが存在するソフトウェアはそこに投資することで成果物を利用するという2つの方法のバランスをとって、開発を迅速に進めるとともにコストを低く抑えることに成功したわけだ。

OpenSSLやNTPdのプロジェクトが危機的状況にある/あったということは、今は広く知られている。継続性を求めて資金援助をしたり、新たに別の類似プロジェクトが立ち上がったりした。必要とされているなら誰かがアクションを起こすだろうし、誰もアクションを起こさないのであれば自然となくなる。

利用者のコミュニティの継続性は、開発者コミュニティに継続性が前提になっている。開発者のコミュニティに継続性が欲しいなら、話は単純だ。何らかの形で支援すれば良い。少なくともOSSを理解している個人や企業は、みんなすでにそうしている。「OSSプロジェクトのソフトウェア資産だけを使い、サポート会社にお金を払う」という関わり方は、開発者コミュニティの継続性に寄与しない。開発者コミュニティとのつき合い方を理解しているサポート会社もあるので、常にそうだとは言わないけれど。

プロジェクトの趨勢を心配するひとは、いつもプロジェクトの外にいる

「BSDは統合されたほうが良い」「もうBSDは滅びてもいいんじゃ」という感想は、時おり目にする。わたしはFreeBSDのコアチームに入って10年以上経つ。プロジェクト運営側の立場として言わせてもらえば、正直どうでも良くて反論する気も起きない。おそらく「同じようなものが複数あって意味あるの?」という点が気になってしょうがないのだと思う。わたしにはこれらが「ヤマトと佐川は統合されたほうが良い」「パンがあるから白米は滅びてもいいんじゃ」という意見と大きな違いがないように見えるのだ。

利用者の視点では、頼っているプロジェクトがなくなると困るだろうし、興味のないプロジェクトはなくなっても構わないと考えると思う。だから世代交替が必要だとか、逆に滅びていいんじゃとか、そういう感想が出てくるのだろう。けれど、開発者コミュニティはそういう感想には興味がない。結局のところ、気の合う仲間と開発するのが楽しくてやっているのだ。「似たようなものをつくるよりも統一すべき、そのほうが利用者にとってもプラス」というような意見に耳を傾けることはない。「GNU Hurd ってまだやってるの? もう止めたら?」なんて大きなお世話だろう。

その一方で、すでに利用者がほとんどいなくなったOSSの利用者コミュニティについて、その存在が継続することに疑問を持つのは正しい感性だと思う。そのような状況では、まずその構成員が一番最初にその疑問を持ち始めるのだと思うが、疑問に思うなら辞めれば良い。OSSの利用者のコミュニティに入ったり出たりすることは、よほど特殊なものでない限り、とても簡単だ。

継続性の議論は重要か?

利用者のコミュニティにおいて世代交替などの継続性を議論することにあまり意味はないと思う。前述のとおり、構成員の大半は何かを得ようという動機を持っていて、何かを提供しようという動機が薄いからだ。人数の増減はソフトウェアへの興味が反映されているので、コミュニティを維持したいのであれば興味を持続させなければならない。しかし、そもそもコミュニティの発生は「興味があって情報が欲しいから」に基づいているので、興味を持続させる努力をしようというのはコミュニティの存在意義と矛盾する。興味が失われた時点で、コミュニティはもう必要とされてない。

「自分が使ってるソフトウェアがなくなると困るなあ」という感想を、プロジェクト運営論にすり替えるのは論点を間違えている。プロジェクトが消滅してしまうような状況になったら、自分でメンテナンスするか、消滅する前にメンテナンスしてくれるひとを何らかの形で支援して継続を求めるのがOSSに対する適切なアプローチだ。この時、利用者のコミュニティがその支援の受け皿になってくれるケースは残念ながら少ないように思う。

すでに述べたとおり、開発者コミュニティは何かを作るという目的を共有している点が利用者コミュニティと決定的に異なる。開発者が興味を失った時点でプロジェクトやコミュニティが消滅してしまうのは変わらないが、個々の構成員による「何かを提供しよう」という動機からつくられるコミュニティはそもそも継続性が高い。たとえばわたしが長年使っているウィンドウマネージャであるWindow Makerのプロジェクトは、1997年から2005年くらいまで活発に開発が続けられ、そこから2012年まで開発は停滞した。その後また開発が再開され、今のリリースは2015年に公開されたものだ。もう使っているひとも少ないと思うが、利用者も開発者も続いている。もし本当にプロジェクトが閉じるような事態になれば、誰かが引き継ぐだろう。

プロジェクトを運営している立場にいるひとでない限り、「世代交替をどうすべきか」という類の議論は不毛に思う。プロジェクトの未来を考えた時、もちろんコミュニティが大きく成長していくことは重要だ。GNUやApache, FreeBSDなどのプロジェクトは、すべてfoundationという法人を設立することでコミュニティの継続性を確保する努力を続けている。しかし、利用者のコミュニティはコントロールできるものではないので、成長を促す努力はまず、開発者コミュニティに向けられるものである。利用者の目に魅力的に映るソフトウェアはコミュニティが活発になるだろうし、そうでなければ縮小する。

仮にプロジェクトで中心的な開発者が意欲を失ったとしても、「コミュニティの中で合意をとる意思と力があり、活動的な開発者」がそのプロジェクトの未来を方向づけるだけだ。FreeBSDプロジェクトは、創立初期のメンバが去ってから久しい。しかし、メンバが去っていく過程で「世代交替しなければ」という使命感が生まれることはなかったし、小さくなっていくコミュニティに危機感を持ったりはしなかった。逆に、年寄りと若者の意見が激しく衝突するような事態になれば、行きつくところはプロジェクトの分裂だと思う。FreeBSDからDragonfly BSDが分離したのは、年寄りとは言わないけれど意見の衝突によるものだった。いずれにしても、プロジェクトやコミュニティの存続において危機的な状況は生まれていない。

開発者の多くは、組織としてこうすべきとか、こうあるべきといった議論に興味がない。特に利用者の視点から発せられる「べき」論は現実味がないものが多く、議論しても得られるものは少ないと思う。

利用者のコミュニティに対して否定的な意見ばかり書いているように読めるかも知れないが、その存在自体が悪いことであるとは思わない。問題は、そのコミュニティが目指しているものと、それに参加しているひとに意識のずれがあると、不幸だということだ。わたしは、自身のOSSとの関わりを利用者コミュニティへの参加、具体的には文書の翻訳からスタートした。日本語翻訳は利用者コミュニティの側で意欲的なひとが作業に携わるケースが多く、とっつきやすそうに見えるのだが、とても大変で継続の難しい作業である。成果もなかなか評価されない。自発的にこのような作業に携わったひとたちは、大半が疲弊して去っていく。支援しなければ消えてしまう可能性の高い活動なのに、こういう活動を支えることができる利用者のコミュニティは少ない。

ひとつアドバイスをするのであれば、何か成果物を生み出す作業をしていて、それが自己満足の領域を越え、何らかの貢献として評価されて欲しいと考えているのであれば、開発者コミュニティに属するべきだということだ。「利用者か開発者か」は明瞭に区別できないことがあるが、開発者コミュニティから距離がある利用者だけのコミュニティは、すでに述べたとおり何かを生み出そうという動機づけが弱いので、活動に協力してくれるひとを探すのが難しい。「日本語の資料の拡充」という活動に対して、何らかの支援をする団体や個人はいるだろうか? OSSは無料だ、コストがかからないという発想のみを持つひとは少なくない。一方、開発者のコミュニティならば、何かを生み出そうとしているひとを何らかのかたちで支援しようとしてくれるだろう。

継続性の高い利用者のコミュニティの一形態は、商業的な活動をしている個人や団体が参画しているケースである。具体的にはサポート業者や教育サービスベンダといった業種の人々が入っていると、生産的な活動やコミュニケーションが活発になる。しかし、どちらにしても活動している団体や個人のモチベーションが尽きたところで活動はおしまいだ。

FreeBSD開発者の共用マシンのmotdには、「Now, shut up and code. Really.」という自戒を込めたメッセージが入れてある。コードから離れて抽象的な議論ばかりに時間を費やすようになってしまったら、本末転倒だからだ。開発者コミュニティの継続性は、コードを書くひとによって達成される。プロジェクトの運営に関する議論はもちろん大事だが、コードを書くという作業が続いているなら継続性の心配は要らない。

FreeBSD 10から追加されたunmapped I/Oという機能がある。これが入ってからSSDのIOPSが格段に改善したが、技術解説が見当たらないのでメモを兼ねてまとめてみた。そもそも筋があまりよくなかった部分を直したようなものなので、新機能というよりはバグ修正と呼んだほうが良いのかも知れない。

FreeBSDのI/Oの問題点

FreeBSD 9までのI/Oは、I/O処理の前に必ずKVA(カーネル仮想アドレス空間)に物理メモリをマップするようになっていた。これは他のBSD由来のOSにはないFreeBSD特有の特徴で、デバイスドライバからデータをカーネル空間のバッファで受け取るために行なわれる。デバイスドライバはマップした仮想アドレス空間を受け取り、対応する物理アドレスに対してDMAを実行するか、あるいはCPUを使ってデータをコピー(いわゆるprogrammed I/O)する。

たとえばユーザランドからread(2)システムコールを発行すると、次のような流れでデータがユーザランドアドレス空間のバッファに移される。

  1. カーネルは、I/O用のバッファをKVAにマップしてデバイスドライバに伝える。
  2. デバイスドライバはデータを読み取ってバッファに転送する。
  3. カーネルはバッファにデータが入った後に、read(2)システムコールで渡されたユーザランド空間のバッファにデータを転送する。
  4. バッファをアンマップしてシステムコールから復帰する。

この流れの問題点は 1 と 4 にある。カーネルは必ずI/O用のバッファをKVAにマップするため、SMP環境ではTLBをクリアするためのIPIが発生する。IPIはアンマップする時にも発生するため、I/Oが発生するたびにTLB shootdownが発生してしまう。

カーネルI/Oバッファの削除

もともとこのバッファのマッピングはバウンスバッファなどの目的で用意されているものだった。近年のアーキテクチャ(特に64-bitのもの)は仮想アドレス空間をダイレクトマップにしているか、TLBをクリアしなくてもダイレクトマップが使えるものが多い。また、デバイスやバスコントローラ側がMMUを備えていることも増えていて、物理メモリ空間へのアクセス範囲が制限されるようなことが少なくなった。つまりI/Oのためのバッファがカーネル空間にマップされていなくても、デバイスドライバからは任意の物理アドレスにDMAを実行することができる。したがって、1 のマップを省いて直接ユーザランド空間のバッファにデータを転送すれば無駄がない。また、この処理はI/Oのコードパスに存在するため、md(4)(メモリディスク)のようにI/Oをvnode以外に行なうドライバも影響を受けて本来不要なデータの転送が発生してしまう。unmapped I/O の変更は、基本的にはこういった動作を実装する修正だ。

デバイスドライバが unmapped I/O に対応している場合(対応しているかどうかを示すフラグがある)、カーネルはバッファをKVAにマップせず、デバイスドライバはI/Oの要求アドレスに対して直接データ転送を行なう。ここで、デバイスやバスコントローラ側でアドレス変換ができるなら仮想アドレスが、できないのであれば物理アドレスが渡される。ほとんどのI/OはCAMサブシステムを通過するので、物理アドレスが必要な場合はCAMのCCB(CAM Control Block)からアドレスを受け取ることになる。ユーザランドプログラムが発行したread(2)システムコールの場合、DMAの転送先はユーザランド空間にマップされた物理アドレスだ。

mapped なのか unmapped なのかは I/O 要求を発行する側で選択できるようになっている。次の場合はmappedを要求する必要がある。簡単に言えば、KVAにマップする必要があるのは、カーネルからデータを触る必要がある場合だけだ。

  • 単にユーザランド空間にデータをコピーするのではなく、カーネルの中でデータを解釈したり書き換える必要がある場合
  • カーネルの中で何度も参照され、長い間保持する必要がある場合

たとえば UFS のコードは、メタデータは mapped を、ファイルの内容については unmapped I/O を使うようになった。変わったのは「カーネルのI/OバッファをKVAにマップするかどうか」の部分だけなので、vnode pagerで管理されているバッファキャッシュの機能は変わらない。

得られた性能向上

この変更で、I/Oの大部分で無駄なIPIが発生しなくなった。ひとつひとつのI/Oのスループットはそれほど変わらないが、KVAマップとTLBのクリアによる遅延量が減ったので、CPU負荷の低減が期待できる。開発者が行なった40コアのCPUとSSDを使ったベンチマークでは、IOPSが300Kから1Mを超える値になることが確認されている。CPU boundだったIOPSが向上したわけだ。

FreeBSD 10 以降であれば、この変更がすでに適用されている。何も設定せずとも有効になっているので、ユーザは特に存在を意識する必要はない。Unmapped I/Oが有効な場合と無効な場合との性能差を知りたければ、次のような設定用の loader tunable があるので、それを操作すると良い。デフォルトは1になっているが、0にすると無効になる。

vfs.unmapped_buf_allowed