学習目的のコードリーディングはLinuxの基本コマンドから始めよう

# 急いでる人向け

`more`や`cat`のソースは読み始めるのに丁度いいよ。

# はじめに

※ コードの中身の話はしません

昔話だけど「プログラミングの勉強のためにソースコードを読みたい」という人に、よく「UNIX/Linuxのコマンドのソースを見るといいよ」と言っていたんだけど、アプリ開発(とくにC言語)からしばらく離れてるので、すっかりそういうアプローチがあったことを忘れてて…

で、[最近とあるイベント(飲み会)](https://soudai1025.connpass.com/event/123828/)で、[データベース界隈](https://www.amazon.co.jp/dp/4297104083/)では[大変有名な方](https://twitter.com/soudai1025)に「『昔zakiさんにコマンドのソースみるといいよ』と言われたのが自分の中で大きかった」という話を(私はすっかり忘れていたんだけど…汗)ご本人から聞いて、せっかくの機会なので、以前どんな風にコードリーディングをやっていたかを簡単にまとめてみました。

OSSのコードというとApache Web Serverとかのサーバ系ミドルウェアなんかを想像することが多いかもしれないけれど、そんな __大きな__ プロダクトのソースを最初から読む必要はありません！何事も小さなところから始めましょう！

## 対象読者

MUSTではないですが、こういう人はとくにお勧めです。

- Linuxコマンドをよく使う人(Linuxに限らずCygwin含む…あ、macOSも大丈夫ですね)
- 普段コーディングはよくするけど、ほかの人のコードを見ることが少ない人
- コーディングはするしほかの人のコードも見るけど、自社開発でクローズドなプロダクトのコードしか見る機会がない人
- OSSに興味ある人

以下基本的にC言語の話になりますが、ほかの言語でも同じだと思います。

# なぜLinuxのコマンドのソースなのか

- よく使うコマンドのソースであればどんな処理なのか想像しやすい
    - コードの内容が難しくても、ある程度想像で補いつつ読み進められる
    - 「いつも使うあの機能はどこで実装されているんだ」という視点でも読み進めやすい
    - 知らなかった/隠された機能が見つかることも？
- ソースの全量が少なく最後まで読んでも負担にならない
    - コマンド名=ソース名であることが多く、読み始めやすい
    - エントリポイントも比較的わかりやすい
    - ゴールが短いと達成感も得やすいので続けやすい
    - 逆に例えばLinuxカーネルのソースを読もうと思っても、広大すぎてどこから手を付ければいいかわからない
- 単機能であることが多く、高度な前提知識が不要
    - 本処理がソースファイル1本(プラス、共通処理でいくつか別ソース程度)だったり
- 入手しやすい
    - オープンソースですから
    - 歴史が長いものは(本来の意味で)レガシー

注意点として、機能が豊富なコマンド(OpenSSLとか…)のソースは難易度高いので最初は別のものにしましょう。(目安として、サブコマンドがあるものは大きすぎる)

# ソースコードの入手方法

ググればだいたいすぐに出てくると思うので、改めて書くまでもないかもしれないけど、探し方を簡単に。

- 公式サイト
    - 例えば`cat`のソースでれば[Coreutils - GNU core utilities](http://www.gnu.org/software/coreutils/)に含まれている
- ディストリビューションのサイトのパッケージ検索
    - Debian: [Debian -- パッケージ](https://www.debian.org/distrib/packages)
    - Ubuntu: [Ubuntu – Ubuntu パッケージ検索](https://packages.ubuntu.com/ja/)
    - CentOS、以前あったような気がしたけど見つからない…

    ここに`bin/cat`とかで検索するとどのパッケージに入っているか確認できる。

- パッケージの機能で検索・取得
    - apt
        - コマンドがどのパッケージに入っているか確認

            ```
            zaki@chaource:~$ which cat
            /bin/cat
            zaki@chaource:~$ dpkg -S /bin/cat
            coreutils: /bin/cat
            ```

        - パッケージのソースを取得する(root権限不要)

            ```
            zaki@chaource:~$ ls
            zaki@chaource:~$ apt source coreutils
            Reading package lists... Done
            Need to get 5,294 kB of source archives.
            Get:1 http://ftp.jaist.ac.jp/pub/Linux/ubuntu bionic/main coreutils 8.28-1ubuntu1 (dsc) [2,302 B]
            Get:2 http://ftp.jaist.ac.jp/pub/Linux/ubuntu bionic/main coreutils 8.28-1ubuntu1 (tar) [5,252 kB]
            Get:3 http://ftp.jaist.ac.jp/pub/Linux/ubuntu bionic/main coreutils 8.28-1ubuntu1 (asc) [1,196 B]
            Get:4 http://ftp.jaist.ac.jp/pub/Linux/ubuntu bionic/main coreutils 8.28-1ubuntu1 (diff) [37.9 kB]
            Fetched 5,294 kB in 3s (1,621 kB/s)
            dpkg-source: info: extracting coreutils in coreutils-8.28
            dpkg-source: info: unpacking coreutils_8.28.orig.tar.xz
            dpkg-source: info: unpacking coreutils_8.28-1ubuntu1.debian.tar.xz
            dpkg-source: info: applying 61_whoips.patch
            dpkg-source: info: applying 63_dd-appenderrors.patch
            dpkg-source: info: applying 72_id_checkngroups.patch
            dpkg-source: info: applying 80_fedora_sysinfo.patch
            dpkg-source: info: applying 85_timer_settime.patch
            dpkg-source: info: applying 99_kfbsd_fstat_patch.patch
            dpkg-source: info: applying 99_float_endian_detection.patch
            zaki@chaource:~$ ls
            coreutils-8.28/                        coreutils_8.28.orig.tar.xz
            coreutils_8.28-1ubuntu1.debian.tar.xz  coreutils_8.28.orig.tar.xz.asc
            coreutils_8.28-1ubuntu1.dsc
            zaki@chaource:~$ 
            ```

    - Yum
        - パッケージの検索

            ```
            [vagrant@vagrant-01 ~]$ which grep
            alias grep='grep --color=auto'
                    /usr/bin/grep
            [vagrant@vagrant-01 ~]$ rpm -qf /usr/bin/grep
            grep-2.20-3.el7.x86_64
            [vagrant@vagrant-01 ~]$ 
            ```

        - ソースの取得
            - aptと異なりソースを取得する標準機能はまだないっぽいので、`yum-utils`パッケージに含まれている`yumdownloader`コマンドを使用する。

            ```
            [vagrant@vagrant-01 ~]$ which yumdownloader
            /usr/bin/yumdownloader
            ```

            ```
            [vagrant@vagrant-01 ~]$ yumdownloader --source grep-2.20-3.el7.x86_64
            Loaded plugins: fastestmirror
            Enabling updates-source repository
            Enabling base-source repository
            Enabling extras-source repository
            base                                                     | 3.6 kB     00:00     
            base-source                                              | 2.9 kB     00:00     
            extras                                                   | 3.4 kB     00:00     
            extras-source                                            | 2.9 kB     00:00     
            updates                                                  | 3.4 kB     00:00     
            updates-source                                           | 2.9 kB     00:00     
            (1/7): base/7/x86_64/group_gz                              | 166 kB   00:00     
            (2/7): extras/7/x86_64/primary_db                          | 188 kB   00:00     
            (3/7): updates/7/x86_64/primary_db                         | 4.2 MB   00:00     
            (4/7): extras-source/7/primary_db                          |  25 kB   00:00     
            (5/7): updates-source/7/primary_db                         | 113 kB   00:00     
            (6/7): base-source/7/primary_db                            | 966 kB   00:07     
            (7/7): base/7/x86_64/primary_db                            | 6.0 MB   00:21     
            warning: /home/vagrant/grep-2.20-3.el7.src.rpm: Header V3 RSA/SHA256 Signature, key ID f4a80eb5: NOKEY
            Public key for grep-2.20-3.el7.src.rpm is not installed
            grep-2.20-3.el7.src.rpm                                    | 1.2 MB   00:01     
            [vagrant@vagrant-01 ~]$ ls
            grep-2.20-3.el7.src.rpm
            [vagrant@vagrant-01 ~]$ 
            ```

            これで`yumdownloader`実行時のディレクトリに、src.rpmファイルが置かれる。
            src.rpmファイルを展開するには`rpm2cpio`コマンドを使用する。

            ```
            [vagrant@vagrant-01 ~]$ rpm2cpio grep-2.20-3.el7.src.rpm | cpio -id
            2522 blocks
            [vagrant@vagrant-01 ~]$ ls
            colorgrep.csh                            grep-2.20-man-fix-gs.patch
            colorgrep.sh                             grep-2.20-pcre-backported-fixes.patch
            grep-2.20-3.el7.src.rpm                  grep-2.20.tar.xz
            grep-2.20-CVE-2015-1345.patch            grep-2.20-w-multibyte-fix.patch
            grep-2.20-egrep-fgrep-symlinks.patch     GREP_COLORS
            grep-2.20-help-align.patch               grepconf.sh
            grep-2.20-long-pattern-speedup.patch     grep.spec
            grep-2.20-man-fixed-regexp-option.patch
            ```

            rpm関連ファイルが出てきただけで、まだソースファイルは展開されていない。`grep-2.20.tar.xz`を展開する。

            ```
            [vagrant@vagrant-01 ~]$ tar xf grep-2.20.tar.xz 
            [vagrant@vagrant-01 ~]$ ls grep-2.20
            ABOUT-NLS   ChangeLog       COPYING       lib          NEWS            src
            aclocal.m4  ChangeLog-2009  doc           m4           po              tests
            AUTHORS     config.hin      gnulib-tests  maint.mk     README          THANKS
            build-aux   configure       GNUmakefile   Makefile.am  README-alpha    TODO
            cfg.mk      configure.ac    INSTALL       Makefile.in  README-release
            [vagrant@vagrant-01 ~]$ 
            [vagrant@vagrant-01 ~]$ ls grep-2.20/src/
            dfa.c        dosbuf.c  grep.h      kwset.h      pcresearch.c   system.h
            dfa.h        egrep.sh  kwsearch.c  Makefile.am  search.h
            dfasearch.c  grep.c    kwset.c     Makefile.in  searchutils.c
            ```

            でてきました。

        - 参考
            - [yumリポジトリからソースファイルをダウンロードするには － ＠IT](https://www.atmarkit.co.jp/flinux/rensai/linuxtips/853downsrpm.html)
            - [＠IT：RPMパッケージからファイルを取り出すには](https://www.atmarkit.co.jp/flinux/rensai/linuxtips/522rpmcpio.html)

    - Cygwin
        - パッケージ名の確認

            ```
            zaki@mascarpone% cygcheck -f /bin/more
            util-linux-2.33.1-1
            ```

        - パッケージのソースコード取得をチェックする

            ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256859/3dc0d55d-6e02-9673-0c8b-a9406e0929d7.png)

            ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256859/8fe333cb-2ebd-9760-0710-49455d0c9863.png)

            ```
            zaki@mascarpone% ls /usr/src/util-linux-2.33.1-1.src
            2.24.2-agetty.patch             2.32.1-testsuite.patch
            2.24.2-libblkid-topology.patch  util-linux.cygport
            2.24.2-libintl.patch            util-linux-2.33.1.tar.xz
            2.25.1-cygwin-include.patch
            zaki@mascarpone% 
            ```

# 読み方の例

あくまで例なので自分のスタイルを見つけてください。

- 小さなコマンドあればだいたい「コマンド名=ソースファイル名」になっているのでそれを探す
- たいていは「`main()`から呼び出される関数」の実装がファイル先頭からいくつかあり最後に`main()`があるので、`main()`から見始める
- 関数先頭はだいたい「変数の定義」「初期処理」「オプションの解析」なので、さらっと流してその次のメイン処理から見る
    - オプションの解析等はそれ自体も(自作ツール作ったりるす際に)参考になったりするので、そこに集中するのもアリ
- 関数呼び出しは関数名からなんとなく意味を察して中を見ずにとりあえず`main()`を読み進める
    - `main()`の中に主処理がない場合は都度、関数の中を見る

# 設定/ツール類

以下、参考情報です。

## aptのソース取得設定

デフォルトではソースの取得がaptlineの設定に入っていないかもしれない。
その場合は、`deb`と同じ内容で`deb-src`の行を追加すればよい。

```
zaki@chaource:~$ apt source coreutils
Reading package lists... Done
E: You must put some 'source' URIs in your sources.list
```

`deb-src`の行を追加する。

```text:/etc/apt/sources.list
deb http://ftp.jaist.ac.jp/pub/Linux/ubuntu bionic main universe restricted multiverse
deb http://ftp.jaist.ac.jp/pub/Linux/ubuntu bionic-security main universe restricted multiverse
deb http://ftp.jaist.ac.jp/pub/Linux/ubuntu bionic-updates main universe restricted multiverse

deb-src http://ftp.jaist.ac.jp/pub/Linux/ubuntu bionic main universe restricted multiverse
deb-src http://ftp.jaist.ac.jp/pub/Linux/ubuntu bionic-security main universe restricted multiverse
deb-src http://ftp.jaist.ac.jp/pub/Linux/ubuntu bionic-updates main universe restricted multiverse
```

変更したら`apt update`して内容を適用する。

```
root@chaource:/etc/apt# apt update
Hit:1 http://ftp.jaist.ac.jp/pub/Linux/ubuntu bionic InRelease
Get:2 http://ftp.jaist.ac.jp/pub/Linux/ubuntu bionic-security InRelease [88.7 kB]
Get:3 http://ftp.jaist.ac.jp/pub/Linux/ubuntu bionic-updates InRelease [88.7 kB]
Get:4 http://ftp.jaist.ac.jp/pub/Linux/ubuntu bionic/universe Sources [9,051 kB]
Get:5 http://ftp.jaist.ac.jp/pub/Linux/ubuntu bionic/multiverse Sources [181 kB]
Get:6 http://ftp.jaist.ac.jp/pub/Linux/ubuntu bionic/main Sources [829 kB]
Get:7 http://ftp.jaist.ac.jp/pub/Linux/ubuntu bionic/restricted Sources [5,324 B]
Get:8 http://ftp.jaist.ac.jp/pub/Linux/ubuntu bionic-security/universe Sources [128 kB]
Get:9 http://ftp.jaist.ac.jp/pub/Linux/ubuntu bionic-security/main Sources [86.0 kB]
Get:10 http://ftp.jaist.ac.jp/pub/Linux/ubuntu bionic-security/restricted Sources [1,504 B]
Get:11 http://ftp.jaist.ac.jp/pub/Linux/ubuntu bionic-security/multiverse Sources [2,744 B]
Get:12 http://ftp.jaist.ac.jp/pub/Linux/ubuntu bionic-security/main amd64 Packages [331 kB]
Get:13 http://ftp.jaist.ac.jp/pub/Linux/ubuntu bionic-security/main Translation-en [118 kB]
Get:14 http://ftp.jaist.ac.jp/pub/Linux/ubuntu bionic-security/universe amd64 Packages [242 kB]
Get:15 http://ftp.jaist.ac.jp/pub/Linux/ubuntu bionic-security/universe Translation-en [139 kB]
Get:16 http://ftp.jaist.ac.jp/pub/Linux/ubuntu bionic-updates/main Sources [264 kB]
Get:17 http://ftp.jaist.ac.jp/pub/Linux/ubuntu bionic-updates/universe Sources [236 kB]
Get:18 http://ftp.jaist.ac.jp/pub/Linux/ubuntu bionic-updates/restricted Sources [2,068 B]
Get:19 http://ftp.jaist.ac.jp/pub/Linux/ubuntu bionic-updates/multiverse Sources [4,612 B]
Get:20 http://ftp.jaist.ac.jp/pub/Linux/ubuntu bionic-updates/main amd64 Packages [597 kB]
Get:21 http://ftp.jaist.ac.jp/pub/Linux/ubuntu bionic-updates/main Translation-en [219 kB]
Get:22 http://ftp.jaist.ac.jp/pub/Linux/ubuntu bionic-updates/universe amd64 Packages [931 kB]
Get:23 http://ftp.jaist.ac.jp/pub/Linux/ubuntu bionic-updates/universe Translation-en [270 kB]
Fetched 13.8 MB in 7s (1,920 kB/s)                                             
Reading package lists... Done
Building dependency tree       
Reading state information... Done
82 packages can be upgraded. Run 'apt list --upgradable' to see them.
root@chaource:/etc/apt# 
```

## Visual Studio Code …と思ったけど、愛用のエディタでどうぞ

VS Codeであれば`src`ディレクトリをVS Codeで開く。
`C/C++`の拡張機能が入っていなければ入れる。

これで関数ジャンプなどの便利機能がすぐに使えるようになる。

他にはctags・etagsや、grepコマンド等を併用しても良い。

## コードフォーマッタ

宗教上の理由などでどうしても書かれているコードのスタイルがなじまなくて読みづらい場合は、そこで苦労するよりは、エディタの機能や[AStyle](http://astyle.sourceforge.net/)などを使って思い切ってフォーマットをかけて読みやすいように変換しましょう。
とくにcoreutilsのgnuスタイルは所見だと制御構文まわりはちょっと癖があるのでstroustrupとかの方がいいかも。

## Doxygen + Graphviz

今ってこの辺のドキュメンテーションツールのデファクトってなんだろう。
[Doxygen](http://www.doxygen.jp/)を使うとソース内のコメントからドキュメンテーションしてくれるけど、[Graphviz](https://www.graphviz.org/)を連携させることで図の生成もできるようになり、関数のcall/callerグラフも作ってくれて、処理の流れをより追いやすくなる。

(cat.cの作成例 / `main()`から`usage()`をコールしている、という図)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256859/62d45b20-f173-fd87-34f8-1e4fbbb1c417.png)

## 書籍

[Code Reading ~オープンソースから学ぶソフトウェア開発技法~ (プレミアムブックス版) | Diomidis Spinellis, まつもと ゆきひろ, 平林 俊一, 鵜飼 文, トップスタジオ |本 | 通販 | Amazon](https://www.amazon.co.jp/dp/4839956693/)

こんなも本ある。
以前読んだんだけど、、探したけど見つからない…

昔書いた感想メモにこんなこと書いてた。

>> 低品質のコードは、次の点に着目すればすぐ見分けることができます。
>>
>> - コーディングスタイルに一貫性がない
>> - 意味もなく複雑な構造や理解不能な構造が使われている
>> - 明らかな論理ミスや手抜きがある
>> - 移植性のない構造が多用されている
>> - 保守されていない
>
> おぉ、全部当てはまってる…

---

自分にあった愛読書(ソースコード)をみつけてください。