2012年12月6日木曜日

Install Cloudera Manager and CDH4 manually


It seems to me that the Cloudera Manager Installer, cloudera-manager-installer.bin, fails due to timeout in a slow network. So, I gave up using cloudera-manager-installer.bin and decided to install Cloudera Manager and CDH4 manually.

  1. JDK
    • Download an RPM of JDK from Oracle's site.
    • Install the RPM.

      $ rpm -Uvh jdk-7u9-linux-x64.rpm
      

    • Set up alternatives manually if /usr/bin/java has not been replaced.

      $ alternatives --install /usr/bin/java java \
          /usr/java/default/bin/java 3
      
      $ alternatives --config java
      


  2. SE Linux
    • Disalbe SE Linux to follow the Cloudera Manager installation instruction.

      $ vi /etc/selinux/config
          // Set SELINUX=disabled
      

    • Reboot the machine to make the new settings effective.

      $ reboot
      


  3. iptables
    • Disable iptables to follow the Cloudera Manager installation instruction.

      $ service iptables stop
      


  4. PostgreSQL
    • Install PostgreSQL for Cloudera Manager.

      $ yum install postgresql
      $ yum install postgresql-server
      


  5. Cloudera Manager
    • Install tools needed to copy Cloudera Manager repository.

      $ yum install yum-utils createrepo
      

    • Prepare information about Cloudera Manager repository. Create a file named cloudera-manager.repo under /etc/yum.repos.d/ whose content is shown below.

      $ vi /etc/yum.repos.d/cloudera-manager.repo
      

      [cloudera-manager]
      name=Cloudera Manager
      baseurl=http://archive.cloudera.com/cm4/redhat/6/x86_64/cm/4/
      gpgkey=http://archive.cloudera.com/cm4/redhat/6/x86_64/cm/RPM-GPG-KEY-cloudera
      gpgcheck=1
      

    • Copy cloudera-manager repository.

      $ mkdir -p /usr/local/repos
      $ cd /usr/local/repos
      $ reposync -r cloudera-manager
      $ createrepo /usr/local/repos/cloudera-manager
      

    • Change the place of cloudera-manager repository to the cloned repository on the local file system. Open /etc/yum.repos.d/cloudera-manager.repo and change the value of baseurl as follows. Note that the number of slashes after file: is 3.

      $ vi /etc/yum.repos.d/cloudera-manager.repo
      

      baseurl=file:///usr/local/repos/cloudera-manager/
      

    • Import the GPG key of Cloudera Manager.

      $ rpm --import \
          http://archive.cloudera.com/cm4/redhat/6/x86_64/cm/RPM-GPG-KEY-cloudera
      

    • Install components of Cloudera Manager.

      $ yum install cloudera-manager-daemons
      $ yum install cloudera-manager-server
      $ yum install cloudera-manager-server-db
      

    • Initialize the database of Cloudera Manager.

      $ service cloudera-scm-server-db initdb
      


  6. CDH
    • Prepare information about CDH repository. Create a file named cloudera-cdh4.repo under /etc/yum.repos.d/ whose content is shown below.

      $ vi /etc/yum.repos.d/cloudera-cdh4.repo
      

      [cloudera-cdh4]
      name=Cloudera's Distribution for Hadoop, Version 4
      baseurl=http://archive.cloudera.com/cdh4/redhat/6/x86_64/cdh/4/
      gpgkey=http://archive.cloudera.com/cdh4/redhat/6/x86_64/cdh/RPM-GPG-KEY-cloudera
      gpgcheck=1
      

    • Copy cloudera-cdh4 repository.

      $ cd /usr/local/repos
      $ reposync -r cloudera-cdh4
      $ createrepo /usr/local/repos/cloudera-cdh4
      

    • Change the place of cloudera-cdh4 repository to the cloned repository on the local file system. Open /etc/yum.repos.d/cloudera-cdh4.repo and change the value of baseurl as follows. Note that the number of slashes after file: is 3.

      $ vi /etc/yum.repos.d/cloudera-cdh4.repo
      

      baseurl=file:///usr/local/repos/cloudera-cdh4/
      

    • Import the GPG key of CDH.

      $ rpm --import \
          http://archive.cloudera.com/cdh4/redhat/6/x86_64/cdh/RPM-GPG-KEY-cloudera
      

    • Install components of CDH. The example below installs ZooKeeper, HDFS, MapReduce and HBase onto a single machine.

      $ yum install hadoop-0.20-mapreduce-jobtracker
      $ yum install hadoop-hdfs-namenode
      $ yum install hadoop-hdfs-secondarynamenode
      $ yum install hadoop-0.20-mapreduce-tasktracker
      $ yum install hadoop-hdfs-datanode
      $ yum install hadoop-client
      $ yum install zookeeper-server
      $ yum install hbase
      


  7. Cloudera Manager Agent
    • Install Cloudera Manager Agent onto machines to be managed by Cloudera Manager.

      $ yum install cloudera-manager-agent
      

    • Edit the configuration file of Cloudera Manager Agent to set a proper value to server_host.

      $ vi /etc/cloudera-scm-agent/config.ini
      


  8. Cluster Setup
    • Start Cloudera Manager.

      $ service cloudera-scm-server-db start
      $ service cloudera-scm-server start
      

    • Start Cloudera Manager Agent.

      $ service cloudera-scm-agent start
      

    • Access the Web UI of Cloudera Manager by a web browser. If your Cloudera Manager is running on localhost, access the URL below. The default ID and password to login the page are "admin" and "admin".

      http://localhost:7180/

    • Setup your cluster by Cloudera Manager Web UI. Note that you can skip the steps to install CDH onto cluster machines via Cloudera Manager because you have already done it.


Cloudera Manager と CDH4 を手作業でインストールする


Cloudera Manager のインストーラーである cloudera-manager-installer.bin は、速度の遅いネットワークではタイムアウトのせいで失敗してしまう (ように見える)。そこで、cloudera-manager-installer.bin を使うことを諦め、手作業で Cloudera Manager と CDH4 をインストールすることにした。

  1. JDK
    • Oracle のサイトから JDK の RPM をダウンロードする。
    • RPM をインストールする。

      $ rpm -Uvh jdk-7u9-linux-x64.rpm
      

    • /usr/bin/java が置き換わっていない場合は手作業で alternatives を設定する。

      $ alternatives --install /usr/bin/java java \
          /usr/java/default/bin/java 3
      
      $ alternatives --config java
      


  2. SE Linux
    • Cloudera Manager のインストール手順に従い、SE Linux を無効にする。

      $ vi /etc/selinux/config
          // SELINUX=disabled と設定する。
      

    • 新しい設定を有効にするため、マシンを再起動する。

      $ reboot
      


  3. iptables
    • Cloudera Manager のインストール手順に従い、iptables を無効にする。

      $ service iptables stop
      


  4. PostgreSQL
    • Cloudera Manager が使うので、PostgreSQL をインストールする。

      $ yum install postgresql
      $ yum install postgresql-server
      


  5. Cloudera Manager
    • Cloudera Manager リポジトリをコピーするのに必要なツールをインストールする。

      $ yum install yum-utils createrepo
      

    • Cloudera Manager リポジトリの情報を用意する。cloudera-manager.repo という名前のファイルを /etc/yum.repos.d/ ディレクトリに作成し、下記の内容を書き込む。

      $ vi /etc/yum.repos.d/cloudera-manager.repo
      

      [cloudera-manager]
      name=Cloudera Manager
      baseurl=http://archive.cloudera.com/cm4/redhat/6/x86_64/cm/4/
      gpgkey=http://archive.cloudera.com/cm4/redhat/6/x86_64/cm/RPM-GPG-KEY-cloudera
      gpgcheck=1
      

    • cloudera-manager リポジトリをコピーする。

      $ mkdir -p /usr/local/repos
      $ cd /usr/local/repos
      $ reposync -r cloudera-manager
      $ createrepo /usr/local/repos/cloudera-manager
      

    • cloudera-manager リポジトリの場所を、ローカルファイルシステム上にコピーしたリポジトリへと変更する。/etc/yum.repos.d/cloudera-manager.repo を開き、baseurl の値を次のように変更する。file: の後に続くスラッシュの数は 3 なので注意。

      $ vi /etc/yum.repos.d/cloudera-manager.repo
      

      baseurl=file:///usr/local/repos/cloudera-manager/
      

    • Cloudera Manager の GPG キーをインポートする。

      $ rpm --import \
          http://archive.cloudera.com/cm4/redhat/6/x86_64/cm/RPM-GPG-KEY-cloudera
      

    • Cloudera Manager のコンポーネントをインストールする。

      $ yum install cloudera-manager-daemons
      $ yum install cloudera-manager-server
      $ yum install cloudera-manager-server-db
      

    • Cloudera Manager のデータベースを初期化する。

      $ service cloudera-scm-server-db initdb
      


  6. CDH
    • CDH リポジトリの情報を用意する。cloudera-cdh4.repo という名前のファイルを /etc/yum.repos.d/ ディレクトリに作成し、下記の内容を書き込む。

      $ vi /etc/yum.repos.d/cloudera-cdh4.repo
      

      [cloudera-cdh4]
      name=Cloudera's Distribution for Hadoop, Version 4
      baseurl=http://archive.cloudera.com/cdh4/redhat/6/x86_64/cdh/4/
      gpgkey=http://archive.cloudera.com/cdh4/redhat/6/x86_64/cdh/RPM-GPG-KEY-cloudera
      gpgcheck=1
      

    • cloudera-cdh4 リポジトリをコピーする。

      $ cd /usr/local/repos
      $ reposync -r cloudera-cdh4
      $ createrepo /usr/local/repos/cloudera-cdh4
      

    • cloudera-cdh4 リポジトリの場所を、ローカルファイルシステム上にコピーしたリポジトリへと変更する。/etc/yum.repos.d/cloudera-cdh4.repo を開き、baseurl の値を次のように変更する。file: の後に続くスラッシュの数は 3 なので注意。

      $ vi /etc/yum.repos.d/cloudera-cdh4.repo
      

      baseurl=file:///usr/local/repos/cloudera-cdh4/
      

    • CDH の GPG キーをインポートする。

      $ rpm --import \
          http://archive.cloudera.com/cdh4/redhat/6/x86_64/cdh/RPM-GPG-KEY-cloudera
      

    • CDH のコンポーネントをインストールする。下記の例では、一台のマシン上に ZooKeeper, HDFS, MapReduce, HBase をインストールしている。

      $ yum install hadoop-0.20-mapreduce-jobtracker
      $ yum install hadoop-hdfs-namenode
      $ yum install hadoop-hdfs-secondarynamenode
      $ yum install hadoop-0.20-mapreduce-tasktracker
      $ yum install hadoop-hdfs-datanode
      $ yum install hadoop-client
      $ yum install zookeeper-server
      $ yum install hbase
      


  7. Cloudera Manager Agent
    • Cloudera Manager で管理するマシンに Cloudera Manager Agent をインストールする。

      $ yum install cloudera-manager-agent
      

    • Cloudera Manager Agent の設定ファイルを開き、server_host に適切な値を設定する。

      $ vi /etc/cloudera-scm-agent/config.ini
      


  8. クラスタ設定
    • Cloudera Manager を起動する。

      $ service cloudera-scm-server-db start
      $ service cloudera-scm-server start
      

    • Cloudera Manager Agent を起動する。

      $ service cloudera-scm-agent start
      

    • ウェブブラウザで Cloudera Manager のウェブ UI にアクセスする。Cloudera Manager が localhost で実行中であるなら、次の URL にアクセスする。ページにログインするためのデフォルトの ID とパスワードは admin / admin である。

      http://localhost:7180/

    • Cloudera Manager ウェブ UI でクラスタをセットアップする。既に CDH をインストール済みなので、Cloudera Manager を使ってクラスタマシン上に CDH をインストールするという手順はスキップできる。

2012年12月2日日曜日

Newer git than the one installed by "yum install git"

If you want to have a newer git than the one installed by "yum install git", do the following.

// Install /usr/bin/curl-config
$ yum install curl-devel

// Install expat.h
$ yum install expat-devel

// Install openssl/ssl.h
$ yum install openssl-devel

// Install C compiler
$ yum install gcc

// Install Perl's ExtUtils/MakeMaker
$ yum install perl-ExtUtils-MakeMaker

// Clone the git source code by git.
$ git clone git://git.kernel.org/pub/scm/git/git.git

// Check the latest tag
$ cd git
$ git tag
......
v1.8.0-rc2
v1.8.0-rc3
v1.8.0.1
        (<--- Latest)


// Check out the latest version
$ git checkout v1.8.0.1

// Build and install
$ make prefix=/usr/local all
$ make prefix=/usr/local install

// Check if the latest git has been installed.
$ /usr/local/bin/git --version
1.8.0.1





「yum install git」でインストールされる git よりも新しい git がほしい場合は次のようにする。

// /usr/bin/curl-config をインストールする。
$ yum install curl-devel

// expat.h をインストールする。
$ yum install expat-devel

// openssl/ssl.h をインストールする。
$ yum install openssl-devel

// C コンパイラーをインストールする。
$ yum install gcc

// Perl の ExtUtils/MakeMaker をインストールする。
$ yum install perl-ExtUtils-MakeMaker

// Git を使って git のソースコードをクローンする。
$ git clone git://git.kernel.org/pub/scm/git/git.git

// 最新タグを調べる。
$ cd git
$ git tag
......
v1.8.0-rc2
v1.8.0-rc3
v1.8.0.1
        (← 最新)


// 最新バージョンをチェックアウトする。
$ git checkout v1.8.0.1

// ビルドしてインストール
$ make prefix=/usr/local all
$ make prefix=/usr/local install

// 最新の git がインストールされたかどうか調べる。
$ /usr/local/bin/git --version
1.8.0.1


2012年11月29日木曜日

ZK is null on connection event (HBase)

HBase clients (such as HBase shell) may throw an exception with a message "ZK is null on connection event" under some conditions, especially, in my case, on a slow machine.

This exception is raised when ZooKeeperWatcher (a part of HBase client) fails to connect to ZooKeeper in a certain period of time. The code snippet below is an excerpt from ZooKeeperWatcher.java.

// Now, this callback can be invoked before the this.zookeeper is set.
// Wait a little while.
long finished = System.currentTimeMillis() +
   this.conf.getLong("hbase.zookeeper.watcher.sync.connected.wait", 2000);
while (System.currentTimeMillis() < finished) {
  Threads.sleep(1);
  if (this.recoverableZooKeeper != null) break;
}
if (this.recoverableZooKeeper == null) {
  LOG.error("ZK is null on connection event -- see stack trace " +
    "for the stack trace when constructor was called on this zkw",
    this.constructorCaller);
  throw new NullPointerException("ZK is null");
}

We can see that this code is waiting for the milliseconds specified by the property hbase.zookeeper.watcher.sync.connected.wait or 2000 milliseconds (the default value) if the property is not set.

So, a simple solution to stop the exception is to set a long-enough value to the property in hbase-site.xml like below.

<property>
  <name>hbase.zookeeper.watcher.sync.connected.wait</name>
  <value>10000</value>
</property>

At least in my case, the above setting made the exception disappear.

2012年11月28日水曜日

yum リポジトリのセットアップ方法 (createrepo)

http://yum.baseurl.org/wiki/RepoCreate のさらっと訳です(「覚え書き」だけに)。

パッケージリポジトリのセットアップ方法

数多くの RPM パッケージ群を一箇所に集め、yum が動作するシステムに提供したいと思うことがあります。この作業はとても簡単です。

概要

yum が使用するパッケージリポジトリは、一つ以上の RPM と、それらの RPM の情報 (依存関係やファイルリストなど) へのアクセスを容易にする「メタ情報」を含む、単なるディレクトリです。yum はこのディレクトリに、FTP や HTTP、(NFS を含む) ファイル URI を用いてアクセスすることができます。

手順

  1. パッケージを一つのディレクトリに集めます。必要な数だけサブディレクトリを作成することができますが、それらをまとめるトップディレクトリが必要です。そこが、リポジトリを形成する場所となります。

  2. Yum は、各 RPM に格納されている情報をまとめた情報を使用します。この情報は createrepo プログラムで作成します。まだ createrepo がインストールされていなければ、次のコマンドでインストールできます。

    yum install createrepo

    RPM を使用していないマシン上にリポジトリを作ろうとしているなら、http://createrepo.baseurl.org/ から createrepo をダウンロードし、手作業でビルドとインストールをおこないます。

    インストール後、createrepo を実行する必要があります。必須の引数は一つで、リポジトリデータを格納するディレクトリを指定します。手順 1 で作成したパッケージディレクトリが /srv/my/repo だとした場合、次のコマンドを実行します。

    createrepo /srv/my/repo

    表示はどんどん流れていってしまいますが、エラーは起こらないでしょう。最終的に /srv/my/repo/repodata という名前のディレクトリが作成され、そこには少なくとも 4 つのファイルができますが、たぶんもっと多いでしょう。

  3. このリポジトリを yum に認識させるため、yum の設定に .repo ファイルを追加する必要があります。このリポジトリを使いたいシステム上で、/etc/yum.repos.d/ に新しいファイルを作成します。ファイル名は何でもよいですが、拡張子は .repo でなければなりません。仮にこのファイルを myrepo.repo としましょう。

    そのファイルに、次の内容を記述します。

    [myrepo]
    name = This is my repo
    baseurl = url://to/get/to/srv/my/repo/

    このファイルに対する編集作業は以上です。baseurl は、リポジトリへのパスです。当該マシンからリポジトリに直接アクセス可能であるか、もしくはそのリポジトリがファイルシステムとしてマウントされているのであれば、baseurl を次のように記述することができます。

    baseurl = file:///srv/my/repo/
        注意: file: の後ろのスラッシュは二つではなく三つです。

    HTTP もしくは HTTPS 経由でアクセスするなら、次のようになります。

    baseurl = http://servername/my/repo

    クライアント側のリポジトリ設定の詳細については yum.conf の man ページに記述されています。

  4. 以後、/srv/my/repo の変更や削除、新しい RPM パッケージの追加のたびに、リポジトリメタデータを作成しなおす必要があります。手順 2 と同様に createrepo でおこないます。

推奨オプション

単純に createrepo と打つだけでも動作しますし、ほとんどの場合それがもっとも互換性のあるやり方ですが、手元の yum クライアントが新しいものであることが分かっているのであれば、追加でオプションを指定することをお勧めします。

  1. --database

    サーバー側に .sqlite データベースを作成します。全てのクライアントの時間削減に寄与します。このオプションを使用しても何も不都合なことはおきません。というのは、古いバージョンの yum は .sqlite ファイルを無視して .xml ファイルを取得しにいくだけだからです。

  2. --unique-md-filenames

    これにより、全てのメタデータファイルが一意の名前を持つことになります。ミラーを使用している場合、このオプションは一番役に立ちます。ただし 3.2.10 より前のバージョンの yum では正しくクリーンアップできません。

  3. --changelog-limit

    ダウンロード時間を削減するため、changelog のエントリー数を制限します。

  4. --deltas --oldpackagedirs

    ダウンロード時間を削減するため、差分情報を作成します。このデータを活用するためにはクライアント側に yum-presto をインストールしておかなければなりません。

上級オプション

  1. createrepo --update

    リポジトリ内に大量のパッケージがあるとき、数個のパッケージを追加・変更するためだけに全パッケージのメタデータを再生成するのは、時間がかかりすぎます。このような場合は --update が便利です。これまで通りに createrepo を実行しますが、次のように --update オプションを渡します。

    createrepo --update /srv/my/repo

    これにより、メタデータの最終生成日時以降に変更・追加・削除のあった項目だけが更新されます。

  2. createrepo -x パッケージファイル名

    リポジトリディレクトリ内に幾つかパッケージがあるものの、疑うことも知らずにアクセスしてくる人達の目には触れさせたくない、と思ったとしましょう。createrepo コマンドで簡単にパッケージを除外することができます。
    createrepo -x filename -x filename2 -x filename* /srv/my/repo

  3. リポジトリ作成後、次のコマンドでリポジトリに署名することができます。

    gpg --detach-sign --armor repodata/repomd.xml

    これにより repodata ディレクトリ内に repomd.xml.asc ファイルが作成されます。新しい yum であれば、リポジトリのメタデータが、その GPG キーの所有者によるものであるかを検証することができます。

  4. 内部で使用するチェックサムを変更することができます。

    createrepo --checksum /srv/my/repo

    このオプションは、repodata を作成しようとしているマシンの Python のバージョンがクライアントのものよりも新しい場合(もしくは python-hashlib がインストールされている場合) のみ必要です。

createrepo によるパッケージリポジトリの生成・管理についてもっと知りたい場合は、createrepo の man ページ、もしくはこのドキュメント集の他の文書を参照してください。


2012年10月18日木曜日

/etc/init.d/functions の解説

(注:編集中)

対象とする functions ファイルは initscripts-9.03.31 に含まれているもので、下記のようにして取得できる。内容は、2012 年 7 月 9 日にリリースされた CentOS 6.3 に含まれる /etc/init.d/functions と同じ。

$ wget http://vault.centos.org/6.3/os/Source/SPackages/initscripts-9.03.31-2.el6.centos.src.rpm
$ rpm2cpio initscripts-9.03.31-2.el6.centos.src.rpm | cpio -id
$ tar xjf initscripts-9.03.31.tar.bz2
    # --> initscripts-9.03.31/rc.d/init.d/functions


/etc/init.d/functions 内で定義されている関数のリスト

  1. fstab_decode_str()
  2. checkpid()
  3. __readlink()
  4. __fgrep()
  5. __umount_loop()
  6. __umount_loopback_loop()
  7. __pids_var_run()
  8. __pids_pidof()
  9. daemon()
  10. killproc()
  11. pidfileofproc()
  12. pidofproc()
  13. status()
  14. echo_success()
  15. echo_failure()
  16. echo_passed()
  17. echo_waring()
  18. update_boot_stage()
  19. success()
  20. failure()
  21. passed()
  22. warning()
  23. action()
  24. strstr()
  25. confirm()
  26. get_numeric_dev()
  27. is_ignored_file()
  28. is_true()
  29. is_false()
  30. apply_sysctl()
  31. key_is_random()
  32. find_crypto_mount_point()
  33. init_crypto()


/etc/init.d/functions の内容解説

# -*-Shell-script-*-
#
# functions    This file contains functions to be used by most or all
#                    shell scripts in the /etc/init.d directory.
#

# TEXTDOMAIN というシェル変数に initscripts という値を設定する。
# これにより、シェルスクリプト中の $"message" という形式の文字列が、
# メッセージカタログ /usr/share/locale/??/LC_MESSAGES/initscripts.mo
# を用いてローカライズされることになる。メッセージカタログの内容は
# msgunfmt /usr/share/locale/??/LC_MESSAGES/initscripts.mo で
# 確認することができる。
TEXTDOMAIN=initscripts

# ファイル作成時のマスクを明示的に 022 に設定する。
# Make sure umask is sane
umask 022

# コマンド検索パスを明示的に /sbin:/usr/sbin:/bin:/usr/bin に設定する。
# Set up a default search path.
PATH="/sbin:/usr/sbin:/bin:/usr/bin"
export PATH

# シェル変数 COLUMNS が設定されていなければ、COLUMNS を 80 に設定する。
# Get a sane screen width
[ -z "${COLUMNS:-}" ] && COLUMNS=80

# シェル変数 CONSOLETYPE が設定されていなければ、コマンド
# /sbin/consoletype を実行した結果を CONSOLETYPE に設定する。
[ -z "${CONSOLETYPE:-}" ] && CONSOLETYPE="$(/sbin/consoletype)"

# /etc/sysconfig/i18n ファイルが存在し、かつ、シェル変数 NOLOCALE も
# シェル変数 LANGSH_SOURCED も設定されていない場合、
if [ -f /etc/sysconfig/i18n -a -z "${NOLOCALE:-}" -a -z "${LANGSH_SOURCED:-}" ] ; then
    # /etc/profile.d/lang.sh を読み込む(=実行する)。
    . /etc/profile.d/lang.sh 2>/dev/null

    # シェル変数 LANGSH_SOURCED の設定を解除する。
    # avoid propagating LANGSH_SOURCED any further
    unset LANGSH_SOURCED
fi

# シェル変数 BOOTUP が設定されていない場合
# Read in our configuration
if [ -z "${BOOTUP:-}" ]; then
    # /etc/sysconfig/init ファイルが存在する場合は、それを読み込む(=実行する)。
    if [ -f /etc/sysconfig/init ]; then
        . /etc/sysconfig/init

    # /etc/sysconfig/init ファイルが存在しない場合は、シェル変数
    # BOOTUP, RES_COL, MOVE_TO_COL, SET_COLOR_SUCCESS,
    # SETCOLOR_FAILURE, SETCOLOR_WARNING, SETCOLOR_NORMAL,
    # LOGLEVEL に値を設定する。
    else
        # This all seem confusing? Look in /etc/sysconfig/init,
        # or in /usr/do/initscripts-*/sysconfig.txt
        BOOTUP=color
        RES_COL=60

        # カーソルの絶対位置を設定するコマンド。カラム ${RES_COL} に
        # カーソルを移動させる。
        MOVE_TO_COL="echo -en \\033[${RES_COL}G"

        # 成功時の出力に用いる文字属性を設定するコマンド。
        # 「1;32」=「太字/緑」
        SETCOLOR_SUCCESS="echo -en \\033[1;32m"

        # 失敗時の出力に 用いる文字属性を設定するコマンド。
        # 「1;31」=「太字/赤」
        SETCOLOR_FAILURE="echo -en \\033[1;31m"

        # 警告時の出力に用いる文字属性を設定するコマンド。
        # 「1;33」=「太字/黄」
        SETCOLOR_WARNING="echo -en \\033[1;33m"

        # 通常時の出力に用いる文字属性を設定するコマンド。
        # 「0;39」=「デフォルト/デフォルト」
        SETCOLOR_NORMAL="echo -en \\033[0;39m"

        LOGLEVEL=1
    fi

    # シェル変数 CONSOLETYPE の値が serial の場合、シェル変数
    # BOOTUP の値を serial に設定し、その他のシェル変数
    # MOVE_TO_COL, SETCOLOR_SUCCESS, SETCOLOR_FAILURE,
    # SETCOLOR_WARNING, SETCOLOR_NORMAL の値を空にする。
    if ["$CONSOLETYPE" = "serial" ]; then
        BOOTUP=serial
        MOVE_TO_COL=
        SETCOLOR_SUCCESS=
        SETCOLOR_FAILURE=
        SETCOLOR_WARNING=
        SETCOLOR_NORMAL=
    fi
fi


#================================================== 
# 関数 fstab_decode_str()
#
# 第一引数で渡された文字列内の特定のパターンを認識して置換する。
# 例えば、第一引数に "a\040b" という文字列が渡された場合、この関数は
# "a b" と出力する。パターンの置換は fstab-decode コマンドによって行う。
# fstab-decode コマンドの詳細についてはこちら
#
# Interpret escape sequences in an fstab entry
fstab_decode_str() {
    fstab-decode echo "$1"
}


#==================================================
# 関数 checkpid()
#
# 引数に渡されたプロセス ID 群のうち、どれか一つでも実行中であれば 0 を返す。
# そうでなければ 1 を返す。
#
# Check if any of $pid (could be plural) are running
checkpid() {
    # ローカル変数 i を宣言する。
    local i

    # 引数を一つずつ順番に変数 i に代入しながら、do ~ done で
    # 囲まれた処理を実行する。
    for i in $* ; do
        # "/proc/プロセスID" という名前のディレクトリが存在する場合、0 を返す。
        # Linux では実行中のプロセスに対応するディレクトリが "/proc/プロセスID"
        # という名前で作成されるので、そのディレクトリが存在するかどうかで、
        # プロセスが実行中かどうかを判定することができる。
        [ -d "/proc/$i" ] && return 0
    done

    # 引数で指定されたプロセスIDは、どれも実行中ではなかった。
    return 1
}


#==================================================
# 関数 __readlink()
#
# 与えられた引数群一つ一つについて、その値をファイル名とみなし、
# そのファイルがシンボリックリックファイルであればリンク先のファイル名を、
# そうでなければ、そのファイルのファイル名をそのまま表示する。例えば、
# 「ln -s target_file symbolic_file」というコマンドで作成された symbolic_file
# というシンボリックファイルが存在するとき、"__readlink symbolic_file" を
# 実行すると、"target_file" という出力が得られる。
#
__readlink() {
    # __readlink 関数に与えられた引数を全て ls -bl コマンドに渡して実行する。
    # ls コマンドのエラー出力は無視し (2>/dev/null)、標準入力のみを awk
    # コマンドに渡す。awk コマンドでは各行の最後のカラム ($NF) のみを出力する。
    #
    # ls -l コマンドは、シンボリックリンクファイルに対しては、
    # 「シンボリックファイル -> ターゲットファイル」 という出力をおこなう。例えば、
    # 「ln -s target_file symbolic_file」というコマンドで作成された symbolic_file
    # という名前のシンボリックファイルが存在している場合、ls -l は、そのファイルの
    # 情報を示す行の末尾に「symbolic_file -> target_file」という出力をおこなう。
    # awk がこの行を処理しているとき、$NF 変数の内容は target_file となる。
    ls -bl "$@" 2>/dev/null| awk '{ print $NF }'
}


#==================================================
# 関数 __fgrep()
#
# 第一引数で指定した文字列が、第二引数で指定したファイル内に含まれていれば、
# その文字列を含む最初の行を出力して戻り値 0 で処理を終える。指定した文字列が
# 含まれていなければ、何も出力せずに戻り値 1 で処理を終える。
#
__fgrep() {
    # 第一引数を変数 s に、第二引数を変数 f に代入する。
    s=$1
    f=$2

    # 変数 f の値をファイル名とみなし、そのファイルをオープンし、一行ずつ処理する。
    # 各行を一つずつ順番に変数 line に代入し、do ~ done で囲まれた処理を実行する。
    while read line; do
        # 読み込んだ行に、変数 s の値がふくまれているかどうかを strstr 関数で調べ
        # (strstr 関数は後で定義する)、含まれていれば、その行を出力してこの関数を
        # 戻り値 0 で終了する。
        if strstr "$line" "$s"; then
            # 行の内容を出力し、戻り値 0 でこの関数を抜ける。
            echo $line
            return 0
        fi
    done < $f

    # 第一引数で指定された文字列が、第二引数で指定されたファイル内に
    # 見つからなかった。
    return 1
}


#==================================================
# 関数 __umount_loop()
#
# TBW
#
# __umount_loop awk_program fstab_file first_msg retry_msg retry_umount_args
# awk_program should process fstab_file and return a list of fstab-encoded
# paths; it doesn't have to handle comments in fstab_file.
__umount_loop() {
        local remaining sig=
        local retry=3 count

        remaining=$(LC_ALL=C awk "/^#/ {next} $1" "$2" | sort -r)
        while [ -n "$remaining" -a "$retry" -gt 0 ]; do
                if [ "$retry" -eq 3 ]; then
                        action "$3" fstab-decode umount $remaining
                else
                        action "$4" fstab-decode umount $5 $remaining
                fi
                count=4
                remaining=$(LC_ALL=C awk "/^#/ {next} $1" "$2" | sort -r)
                while [ "$count" -gt 0 ]; do
                        [ -z "$remaining" ] && break
                        count=$(($count-1))
                        usleep 500000
                        remaining=$(LC_ALL=C awk "/^#/ {next} $1" "$2" | sort -r)
                done
                [ -z "$remaining" ] && break
                fstab-decode /sbin/fuser -k -m $sig $remaining >/dev/null
                sleep 3
                retry=$(($retry -1))
                sig=-9
        done
}


#==================================================
# 関数 __umount_loopback_loop()
#
# TBW
#
# Similar to __umount loop above, specialized for loopback devices
__umount_loopback_loop() {
        local remaining devremaining sig=
        local retry=3

        remaining=$(awk '$1 ~ /^\/dev\/loop/ && $2 != "/" {print $2}' /proc/mounts)
        devremaining=$(awk '$1 ~ /^\/dev\/loop/ $2 != "/" {print $1}' /proc/mounts)
        while [ -n "$remaining" -a "$retry" -gt 0 ]; do
                if [ "$retry" -eq 3 ]; then
                        action $"Unmounting loopback filesystems: "\
                                fstab-decode umount $remaining
                else
                        action $"Unmounting loopback filesystems (retry):" \
                                fstab-decode umount $remaining
                fi
                for dev in $devremaining ; do
                        losetup $dev > /dev/null 2>&1 && \
                                action $"Detaching loopback device $dev; " \
                                losetup -d $dev
                done
                remaining=$(awk '$1 ~ /^\/dev\/loop/ && $2 != "/" {print $2}' /proc/mounts)
                devremaining=$(awk '$1 ~ /^\/dev\/loop/ && $2 != "/" {print $1}' /proc/mounts)
                [ -z "$remaining" ] && break
                fstab-decode /sbin/fuser -k -m $sig $remaining >/dev/null
                sleep 3
                retry=$(($retry -1))
                sig=-9
        done
}


#==================================================
# 関数 __pids_var_run()
#
# 第一引数で指定されたプログラムが実行中であるかどうかを、当該プログラムの
# プロセス ID ファイルの内容をチェックすることによりおこなう。第二引数が指定
# されていれば、それがプロセス ID ファイルのパス名として扱われる。もしも第二
# 引数が指定されていなければ、"/var/run/プログラムのベース名.pid" という
# パスをプロセス ID ファイルとみなす。ここで、「プログラムのベース名」とは、
# 第一引数で指定されたプログラムパスからディレクトリ部分を取り除いたもの。
#
# 戻り値
#   0 : 当該プログラムのプロセスが一つ以上実行中
#   1 : プロセス ID ファイルは存在するが、そこに列挙されているプロセス ID の
#     いずれも実行中ではない。
#   3 : プロセス ID ファイルが存在しない。
#   4 : プロセス ID ファイルは存在するが、そのファイルを読む権限がない。
#
# 副作用
#   変数 pid に、当該プログラムの実行中プロセス群のプロセス ID 群が
#   スペース区切りで列挙される。実行中のプロセスが一つも無い場合は
#   pid の内容は空になる。
#
# __proc_pids {program} [pidfile]
# Set $pid to pids from /var/run* for {program}.  $pid should be declared
# local in the caller.
# Returns LSB exit code for the 'status' action.
__pids_var_run() {
    # 第一引数からディレクトリ名部分を取り除いたものを、ローカル変数 base に
    # 設定する。${parameter##word} という変数参照は、word によって指定される
    # パターンを、変数 parameter を展開した値から最長一致パターンで取り除いた
    # 値となる。
    local base=${1##*/}

    # 第二引数が指定されていれば、その値をローカル変数 pid_file に設定する。
    # 第二引数が指定されていない場合は、"/var/run/$base.pid" という値を設定する。
    # ($base の部分は、ローカル変数 base の値で置き換えられる)
    # ${parameter:-word} という変数参照は、parameter が設定されていないかもしくは
    # 空文字列の場合 word を展開した値となり、そうでなければ parameter の値となる。
    local pid_file=${2:-/var/run/$base.pid}

    # 変数 pid の内容を空にする。
    pid=

    # 変数 pid_file の値が示すファイルが存在しているなら、
    if [ -f "$pid_file" ] ; then
        # ローカル変数 line, p を宣言する。
        local line p

        # 変数 pid_file の値が示すファイルを読むことができない場合、
        # すなわち、この関数を実行したユーザーが当該ファイルを読む
        # 権限を持っていない場合、戻り値 4 でこの関数を抜ける。
        [ ! -r "$pid_file" ] && return 4 # "user had insufficient privilege"

        # 変数 pid_file の値が示すファイルを読み込み、一行ずつ処理するループ。
        # コロン「:」はシェルの組み込みコマンドで、特に何もせずに終了コード 0 を返す。
        while : ; do
            # 一行読み込んで、その内容を変数 line に設定する。
            read line

            # 変数 line の内容が空であれば無限ループを抜ける。
            [ -z "$line" ] && break

            # 変数 line の内容を空白文字で分割し、各要素の値を変数 p に
            # 順次セットしながら、do ~ done で囲んだ処理を実行する。
            for p in $line ; do
                # 変数 p の値が数字だけで構成されていて、かつ、"/proc/pの値"
                # というディレクトリが存在する場合、変数 pid の内容に変数 p の
                # 値を追加する。
                #
                # ${parameter//pattern/string} という変数参照は、parameter を
                # 展開したのち、pattern にマッチした部分を全て string で置き換えた
                # 値となる。ここでは、pattern が [0-9] で string が空文字列なので、
                # ${p//[0-9]/} は p に含まれる数字を全部取り除くという動作になる。
                # そのため、もしも変数 p の値が全て数字で構成されているなら、
                # ${p//[0-9]/} の値は空となり、「-z "${p//[0-9]/}」は真となる。
                #
                # "/proc/プロセスID" というディレクトリが存在するかどうかで対象の
                # プロセスが実行中かどうかを判定している。checkpid 関数内の
                # 説明も参照のこと。
                [ -z "${p//[0-9]/}" -a -d "/proc/$p" ] && pid="$pid $p"
            done
        done < "$pid_file"

        # 変数 pid の中身が空ではない場合、すなわち、プロセス ID ファイル内に
        # 列挙されているプロセス ID 群のうち、どれか一つでも実行中の場合、
        if [ -n "$pid" ]; then
            # 戻り値 0 でこの関数の処理を終える。
            return 0
        fi

        # プロセス ID ファイルは存在するが、そこに列挙されているプロセス ID 群の
        # いずれも実行中ではない。戻り値 1 でこの関数の処理を終える。
        return 1 # "Program is dead and /var/run pid file exists"
    fi

    # プロセス ID ファイルが存在しない。戻り値 3 でこの関数の処理を終える。
    return 3 # "Program is not running"
}


#==================================================
# 関数 __pids_pidof()
#
# 第一引数で指定された名前を持つプログラムのプロセス ID リストを出力する。
#
# Output PIDs of matching processes, found using pidof
__pids_pidof() {
    # pidof プログラムの -c オプションは、当 pidof プロセスと同じルートディレクトリを
    # 持つプロセスのみを対象とすることを指示する(ただしこれは pidof の実効ユーザ
    # ID が 0 の場合のみ機能する)。
    #
    # -o オプションは、指定されたプロセス ID を表示しないことを指示する。下記の
    # コマンドラインでは、$$、$PPID および %PPID が -o オプションに渡されている。
    # %PPID は pidof が特別に解釈する文字列で、 当 pidof プロセスの親プロセスの
    # プロセス ID を指す。
    #
    # -x オプションは、プログラム(ここではシェルスクリプトが想定される)を実行
    # しているシェルのプロセス ID も表示することを指示する。例えば、次の内容を
    # 持つシェルスクリプトを myscript という名前で作成し、./myscript と起動したとき、
    #
    #        #!/bin/sh
    #        while true; do sleep 60; done
    #
    # 「pidof myscript」とタイプしてもプロセス ID は表示されないが、
    # 「pidof -x myscript」とタイプすると、プロセス ID が表示される。今仮に、
    # 「pidof -x myscript」とタイプしたときに 31437 と表示されたとすると、
    # 「cat /proc/31437/cmdline | tr '\0' ' '」を実行すると、
    # 「/bin/sh ./myscript」と表示される。
    #
    # 下記コマンドラインの末尾の「${1##*/}」という変数展開は、第一引数 $1 の値から
    # ベース名を抽出するためのものである。例えば $1 の値が /aa/bb/cc だとすると、
    # ${1##*/} の値は cc になる。
    pidof -c -o $$ -o $PPID -o %PPID -x "$1" || \
        pidof -c -o $$ -o $$PPID -o %PPID -x "${1##*/}"
}


#==================================================
# 関数 daemon()
#
# 使い方
#     daemon [options] {program}
#
# オプション
#     +/-nicelevel
#         実行するプログラムの nice 値を増減する。このオプションは、そのまま
#         「nice -n オプション」という形で使用される。例えば「+10」というオプションが
#         daemon 関数に渡された場合、内部的に「nice -n +10」が実行される。
#         負の nicelevel を与えるためには適切な特権(通常はスーパーユーザー権限)が
#         必要となる。
#
#     --check base
#     --check=base
#         対象プログラムのベース名を明示的に指定する。このオプションが与えられない
#         場合は、指定されたプログラム名がベース名を求めるために使用される。
#         求められたベース名は、__pids_var_run 関数の第一引数として使用される。
#         __pids_var_run 関数は、プロセス ID ファイルが明示的に指定されないとき、
#         ベース名を元にプロセス ID ファイルのパス名を決める。--pidfile オプションの
#         説明も参照のこと。
#
#     --user user
#     --user=user
#         プログラムを実行するユーザーを明示的に指定する。このオプションが指定
#         されると、runuser コマンドを介して指定されたプログラムが実行される。
#
#     --pidfile file
#     --pidfile=file
#         プログラムのプロセス ID を記録するファイル (プロセス ID ファイル) を指定する。
#         指定されたプロセス ID ファイル名は、__pids_var_run 関数の第二引数として
#         使用される。このオプションが与えられない場合、/var/run/ベース名.pid という
#         ファイルがプロセス ID ファイルとして使用される。
#
#     --force
#         指定されたプログラムのプロセスが既に実行中であっても、強制的に新しい
#         プロセスの起動をおこなう。
#
# その他
#     DAEMON_COREFILE_LIMIT
#         DAEMON_COREFILE_LIMIT という変数に値が設定されていれば、その値が
#         コアダンプファイルの最大ブロック数として使用される (= ulimit -S -c ??? の
#         ??? の部分の値として使用される)。DAEMON_COREFILE_LIMIT という変数が
#         設定されていない場合は、コアダンプファイルのサイズはゼロに設定される。
#
#     CGROUP_DAEMON
#         CGROUP_DAEMON という変数に値が設定されていて、かつ /bin/cgexec が
#         実行可能であれば、プログラム実行時に cgexec を使用する。CGROUP_DAEMON
#         変数の値は、cgexec の -g オプションに渡す値がスペース区切りで列挙されている
#         とみなされる。例えば、CGROUP_DAEMON の値が「xxx yyy zzz」である場合、
#         最終的に「/bin/cgexec -g xxx -g yyy -g zzz」という cgexec 起動コマンドラインが
#         生成されることになる。
#
# A function to start a program.
daemon() {
    # ローカル変数群を宣言する。
    # Test syntax.
    local gotbase= force= nicelevel corelimit
    local pid base= user= nice= bg= pid_file=
    local cgroup=
    nicelevel=0

    # 与えられた引数を順次処理していくループ。現在の一番目の位置パラメータ
    # (すなわち $1)の値が、マイナス記号 '-' もしくはプラス記号 '+' で始まっている
    # 間は、ループを続ける。
    #
    # ${parameter##word} という変数参照は、parameter を展開した値から、word に
    # 指定されたパターンを最長一致で取り除いた値となる。
    while [ "$1" != "${1##[-+]}" ]; do
        # $1 の値によって処理を分岐する。
        case $1 in
            # マイナス記号もしくはプラス記号の後ろに、何も文字が続いていない場合
            # (=マイナス記号もしくはプラス記号が、単独で指定された場合)
            '')
                # 使い方を表示する。
                echo $"$0: Usage: daemon [+/-nicelevel] {program}"

                # 戻り値 1 でこの関数の処理を終える。
                return 1
                ;;

            # オプション --check base
            --check)
                # "--check" の次のコマンドライン引数の値を変数 base に格納する。
                base=$2

                # 明示的にベース名が指定されたことを覚えておく。
                gotbase="yes"

                # "--check" とそれに続くコマンドライン引数の二つの引数を消費したので、
                # 位置パラメータ群の先頭から 2 つ取り除く。
                shift 2
                ;;

            # オプション --check=base
            --check=?*)
                # "--check=base" から "--check=" を取り除いたもの、すなわち base の部分を
                # 変数 base に格納する。
                base=${1#--check=}

                # 明示的にベース名が指定されたことを覚えておく。
                gotbase="yes"

                # "--check=base" を消費したので、位置パラメータ群の先頭から取り除く。
                shift
                ;;

            # オプション --user user
            --user)
                # "--user" の次のコマンドライン引数の値を変数 user に格納する。
                user=$2

                # "--user" とそれに続くコマンドライン引数の二つの引数を消費したので、
                # 位置パラメータ群の先頭から 2 つ取り除く。
                shift 2
                ;;

            # オプション --user=user
            --user=?*)
                # "--user=user" から "--user=" を取り除いたもの、すなわち user の部分を
                # 変数 user に格納する。

                user=${1#--user=}

                # "--user=user" を消費したので、位置パラメータ群の先頭から取り除く。
                shift
                ;;

            # オプション --pidfile
            --pidfile)
                # "--pidfile" の次のコマンドライン引数の値を変数 pid_file に格納する。
                pid_file=$2

                # "--pidfile" とそれに続くコマンドライン引数の二つの引数を消費したので、
                # 位置パラメータ群の先頭から 2 つ取り除く。
                shift 2
                ;;

            # オプション --pidfile=file
            --pidfile=?*)
                # "--pidfile=file" から "--pidfile=" を取り除いたもの、すなわち file の部分を
                # 変数 pid_file に格納する。
                pid_file=${1#--pidfile=}

                # "--pidfile=file" を消費したので、位置パラメータ群の先頭から取り除く。
                shift
                ;;

            # オプション --force
            --force)
                # --force が指定されたことを覚えておく。
                force="force"

                # "--force" を消費したので、位置パラメータ群の先頭から 2 つ取り除く。
                shift
                ;;

            # オプション -数値 / +数値
            [-+][0-9]*)
                # nice という変数に、nice コマンドを起動するコマンドラインを設定する。
                nice="nice -n $1"

                # "-数値" もしくは "+数値" を消費したので、位置パラメータ群の先頭から
                # 取り除く。
                shift
                ;;

            # 未知のオプション
            *)
                # 使い方を表示する。
                echo $"$0: Usage: daemon [+/-nicelevel] {program}"

                # 戻り値 1 でこの関数の処理を終える。
                return 1;;
        esac
    done

    # 「--base base」 もしくは 「--base=base」 で明示的にベース名が指定されて
    # いない場合、現在の位置パラメータ $1 の値 (=上記の while ループで全ての
    # オプションを処理し終わったあとに残っている値=プログラム名)をパス名と
    # みなし、それからディレクトリ名部分を取り除いたものを、変数 base に格納する。
    # Save basename.
    [ -z "$gotbase" ] && base=${1##*/}

    # __pids_var_run 関数を使って、指定されたプログラムが実行中かどうかを調べる。
    # __pids_var_run 関数は、指定されたプログラムが実行中の場合、pid という変数に
    # そのプログラムのプロセス ID をセットする。pid 変数には複数のプロセス ID が
    # セットされるケースもありうる。
    # See if it's already running. Look *only* at the pid file.
    __pids_var_run "$base" "$pid_file"

    # 指定されたプログラムが既に実行中で、かつ、"--force" オプションが与えられて
    # いない場合、何もせずにこの関数の処理を終える。
    [ -n "$pid" -a -z "$force" ] && return

    # corelimit という変数に、コアダンプファイルの最大ブロック数を設定するための
    # コマンドライン文字列を設定する。DAEMON_COREFILE_LIMIT という変数が
    # 明示的に設定されていない場合、コアダンプファイルのサイズはゼロに設定される。
    # make sure it doesn't core dump anywhere unless requested
    corelimit="ulimit -S -c ${DAEMON_COREFILE_LIMIT:-0}

    # 変数 CGROUP_DAEMON が設定されている場合、
    # if they set NICELEVEL in /etc/sysconfig/foo, honor it
    if [ -n "${CGROUP_DAEMON}" ]; then
        # /bin/cgexec が実行可能でない場合、
        if [ ! -x /bin/cgexec ]; then
            # cgroups がインストールされていない旨の警告表示をおこなう。
            echo -n "Cgroups not installed"; warning
            echo
        else
            # cgexec プログラムを起動するためのコマンドラインを生成する。
            # まず、コマンドラインの先頭には cgexec プログラムのパス名を置く。
            cgroup="/bin/cgexec";

            # 変数 CGROUP_DAEMON に列挙されている要素全てについて、
            # それぞれ「-g 要素」という形にして cgexec コマンドラインに追加する。
            for i in $CGROUP_DAEMON; do
                cgroup="$cgroup -g $i";            done
        fi
    fi

    # 変数 BOOTUP の値が verbose で、かつ、変数 LSB の値が空であれば、
    # ベース名を出力する。
    # Echo daemon
    [ "${BOOTUP:-}" = "verbose" -a -z "${LSB:-}" ] && echo -n " $base"

    # プログラムを起動する。--user オプションが明示的に指定されている場合は、
    # runuser を用いてプログラムを起動する。
    # And start it up
    if [ -z "$user" ]; then
        $cgroup $nice /bin/bash -c "$corelimit >/dev/null 2>&1 ; $*"
    else
        $cgroup $nice runuser -s /bin/bash $user -c "$corelimit >/dev/null 2>&1 ; $*"
    fi

    # 上記コマンドの実行結果が 0 (正常終了) であれば、起動に成功した旨の
    # 表示をおこない、そうでなければ、起動に失敗した旨の表示をおこなう。
    [ "$?" -eq 0 ] && success $"$base startup" || failure $"$base startup"
}


#==================================================
# 関数 killproc()
#
# 概要
#     指定されたプログラムのプロセスにシグナルを送る。明示的にシグナル番号が
#     指定されない場合は、プロセスを終了するためのシグナル (TERM, KILL) が
#     送られる。

# 使い方
#     killproc [-p PIDファイル] [-d 終了待機時間] プログラム名 [シグナル番号]
#
# オプション
#     全般的な注意事項
#         この関数の実装の都合により、オプションの出現順序は、「使い方」で
#         示された通りでなければならない。例えば、-p オプションと -d オプションの
#         両方を指定する場合は、-p オプションが -d オプションに先行しなければ
#         ならない。 
#
#    -p PIDファイル
#        プログラムの PID ファイルを指定する。
#
#    -d 終了待機時間
#        KILL シグナルを送る前に待機する秒数を指定する。デフォルトは 3 秒。
#        明示的にシグナル番号が指定されない場合、killproc 関数は対象プロセスに
#        TERM シグナルを送り、少し待機した後、対象プロセスが終了しているかどうかを
#        調べる。まだ終了していなかった場合、終了待機時間だけ待ち、再度プロセスが
#        終了しているかどうかを調べる。それでもまだ終了していなかった場合、KILL
#        シグナルを送る。
#
# 副作用
#     明示的にシグナル番号が指定されなかった場合、プロセス終了後、PID ファイルが
#     削除される。
#
# A function to stop a proram
killproc() {
    # ローカル変数を宣言する。
    local RC killlevel= base pid pid_file= delay

    # 関数の戻り値 (RC) を 0 に、遅延秒数 (delay) を 3 に初期化する。
    RC=0; delay=3

    # この関数に引数が何も渡されなかった場合。引数の数 ($#) がゼロかどうかを
    # 調べることで、引数が渡されたかどうかをチェックする。
    # Test syntax.
    if [ "$#" -eq 0 ]; then
        # この関数の使い方を表示し、戻り値 1 で関数の処理を終える。
        echo $"Usage: killproc [-p pidfile] [ -d delay] {program} [signal]"
        return 1
    fi

    # 「-p PIDファイル」オプションの処理。
    if [ "$1" = "-p" ]; then
        # -p の次の引数の値を変数 pid_file に設定する。
        pid_file=$2

        # "-p" とその次の変数を消費したので、位置パラメータ群から 2 つ取り除く。
        shift 2
    fi

    # 「-d 終了待機秒数」オプションの処理。
    if [ "$1" = "-d" ]; then
        # "-d" の次の引数の値を変数 delay に設定する。
        delay=$2

        # "-d" とその次の変数を消費したので、位置パラメータ群から 2 つ取り除く。
        shift 2
    fi

    # 第二引数があれば、シグナル番号として使用する。
    # check for second arg to be kill level
    [ -n "${2:-}" ] && killlevel=$2

    # 第一引数のベース名を変数 base にセットする。
    # Save basename.
    base=${1##*/}

    # プロセス ID を特定する。__pids_var_run 関数は副作用として変数 pid をセットする。
    # Find pid.
    __pids_var_run "$1" "$pid_file"
    RC=$?

    # __pids_var_run 関数でプロセス ID を特定できなかった場合、
    if [ -z "$pid" ]; then
        # プロセス ID ファイルが指定されていない場合、
        if [ -z "$pid_file" ]; then
            # __pids_pidof 関数を用いてプロセス ID を特定する。
            pid="$(__pids_pidof "$1")"

        # プロセス ID ファイルが指定されている場合、
        else
            # RC の値が 4 ということは、プロセス ID ファイルは存在するものの
            # そのファイルを読むことができなかったことを意味する。プロセス ID を
            # 特定できなかったので、プロセスを KILL することはできない。
            # この関数から抜ける。
            [ "$RC" = "4" ] && { failure $"$base shutdown" ; return $RC :}
        fi
    fi

    # Kill it.

    # これまでの処理でプロセス ID を特定できている場合、
    if [ -n "$pid" ] ; then
        [ "$BOOTUP" = "verbose" -a -z "${LSB:-}" ] && echo -n "$base "

        # 明示的にシグナル番号が指定されていない場合
        if [ -z "$killlevel" ] ; then
            if checkpid $pid 2>&1; then
                # TERM first, then KILL if not dead
                # はじめに TERM シグナルをプロセスに送る。
                kill -TERM $pid >/dev/null 2>&1

                # 0.1 秒待ってみる。
                usleep 100000

                # プロセスがまだ動いていれば、さらに 1 秒待ち、それでもまだプロセスが
                # 動いているようであれば引数で指定された秒数(デフォルトは 3 秒)待ち、
                # それでもまだプロセスが動いていれば KILL シグナルをプロセスに送る。
                # (KILL シグナルは、補足できないシグナルであることに注意)
                if checkpid $pid && sleep 1 &&
                   checkpid $pid && sleep $delay &&
                   checkpid $pid ; then
                       # KILL シグナルを送る。
                       kill -KILL $pid >/dev/null 2>&1

                       # 0.1 秒待ってみる。
                       usleep 100000
                fi
            fi

            # プロセスが動いているかどうか調べる。
            checkpid $pid

            # checkpid 関数の戻り値を変数 RC に設定する。
            RC=$?

            # プロセスが止まっていれば、killproc 関数は成功。止まっていなければ失敗。
            [ "$RC" -eq 0 ] && failure $"$base shutdown" || success $"$base shutdown"

            # 成功時は RC に 0 を、失敗時は 1 を設定する。括弧二つで囲む「((式))」という
            # 表現は、式を算術式として評価する。
            RC=$((! $RC))

        # 明示的にシグナル番号が指定されている場合
        # use specified level only
        else
            # プロセスが動いているならば
            if checkpid $pid; then
                # 指定されたシグナルをプロセスに送る。
                kill $killlevel $pid >/dev/null 2>&1
                RC=$?
                [ "$RC" -eq 0 ] && success $"$base $killlevel" || failure $"$base $killlevel"

            # プロセスが動いていなければ
            elif [ -n "${LSB:-}" ]; then
                RC=7 # Program is not running
            fi
        fi

    # これまでの処理でプロセス ID を特定できていない場合
    else
        if [ -n "${LSB:-}" -a -n "$killlevel" ]; then
            RC=7 # Program is not running
        else
            failure $"$base shutdown"
            RC=0
        fi
    fi

    # Remove pid file if any.
    # シグナル番号が明示的に指定されていなければ、PID ファイルを削除する。
    # (シグナル番号が指定されている場合、その番号は必ずしもプロセスを終了
    # させるための番号であるとは限らないので、むやみに PID ファイルを削除
    # すべきではない)。
    if [ -z "$killlevel" ]; then
        rm -f "${pid_file:-/var/run/$base.pid}"
    fi

    return $RC
}


#==================================================
# 関数 pidfileofproc()
#
# 第一引数で指定されたプログラムが実行中であれば、そのプログラムのプロセス
# ID 群を表示する。この関数は、プログラムが実行中であるかを調べる際、プロセス
# ID ファイルのみをチェックする。プロセス ID ファイルのパスは、
# 「/var/run/プログラムのベース名.pid」である。
#
# 第一引数が指定されていない場合、使い方を表示し、戻り値 1 で関数は終了する。
# 第一引数が指定されている場合、プログラムが実行中であるか否かに関係なく
# 戻り値は常に 0 となる。
#
# A function to find the pid of a program. Looks *only* at the pidfile
pidfileofproc() {
    local pid

    # 引数が指定されていない場合。「$#」は位置パラメータの数を意味するが、
    #  この時点で位置パラメータには関数の引数が入っているので、すなわち、
    # ここでは $# は関数の引数の数ということになる。
    # Test syntax.
    if [ "$#" = 0 ] ; then
        # 使い方を表示して、戻り値 1 で関数を抜ける。
        echo $"Usage: pidfileofproc {program}"
        return 1
    fi

    # 第一引数で指定されたプログラムが実行中であるかどうかを、関数
    # __pids_var_run を用いて調べる。__pids_var_run は、プログラムが
    # 実行中であれば、変数 pid にプロセス ID 群をセットする。
    __pids_var_run "$1"

    # 変数 pid の値の長さがゼロでなければ、その内容を表示する。
    [ -n "$pid" ] && echo $pid

    # 戻り値 0 で関数を抜ける。
    return 0
}


#==================================================
# 関数 pidofproc()
#
# 概要
#     プログラムが実行中であれば、そのプロセス ID 群を表示する。
#
# 使い方
#     pidofproc   [-p プロセスIDファイル]   プログラム名
#
# 動作説明
#     この関数は、引数にプログラム名を取る。プログラム名が指定されていない
#     場合は、戻り値 1 で終了する。プログラム名が指定されていれば、まず、
#     プロセス ID ファイルを調べ、プロセスが存在するかチェックする。プロセス
#     ID ファイルのデフォルト値は、「/var/run/プログラムのベース名.pid」であるが、
#     オプション「-p プロセスIDファイル」により、チェックする対象のプロセス ID
#     ファイルを変更することができる。プロセス ID ファイルをチェックすることにより
#     プロセスが存在することが確認できた場合は、そのプロセス ID 群を表示し、
#     戻り値 0 で関数は終了する。
#
#     プロセス ID ファイルをチェックした結果、プロセスが見つからなかった場合、
#     -p オプションで明示的にプロセス ID ファイルが指定された場合を除き、
#     次に、pidof を用いて対象プログラムが実行中であるかどうかを調べる。
#
#     プログラムが実行中であれば、そのプロセス ID 群を表示し、戻り値 0 で関数は
#     終了する。プロセスが実行中でなければ、0 以外の値で関数は終了する。
#
# A function to find the pid of a program.
pidofproc() {
    # ローカル変数 RC, pid, pid_file を宣言する。
    local RC pid pid_file=

    # 引数が一つも渡されていない場合、
    # Test syntax.
    if [ "$#" = 0 ]; then
        # 使い方を表示して、戻り値 1 で関数を抜ける。
        echo $"Usage: pidofproc [-p pidfile] {program}"
        return 1
    fi

    # 第一引数の値が -p の場合、
    if [ "$1" = "-p" ]; then
        # "-p" の次の引数の値をプロセス ID ファイルとみなす。
        pid_file=$2

        # "-p" とその後ろの引数を消費したので、位置パラメータから二つ取り除く。
        shift 2
    fi

    fail_code=3 # "Program is not running"

    # まず、プロセス ID ファイルを調べる。
    # First try "/var/run/*.pid" files
    __pids_var_run "$1" "$pid_file"

    # 関数 __pids_var_run の戻り値を変数 RC にセットする。
    RC=$?

    # 変数 pid の値の長さがゼロでない場合、 すなわち、__pids_var_run 関数の
    # 引数として指定したプログラムが実行中である場合、
    if [ -n "$pid" ]; then
        # 当該プログラムのプロセス ID 群を表示し、戻り値 0 でこの関数を抜ける。
        echo $pid
        return 0
    fi

    # プロセス ID ファイルが関数の引数として明示的に指定されていたのであれば、
    # これ以上は調べずに、関数 __pids_var_run の戻り値をこの関数の戻り値とし、
    # この関数を抜ける。
    [ -n "$pid_file" ] && return $RC

    # プロセス ID ファイルをチェックしたところ、プログラムは実行中ではなかった。
    # 次は、関数 __pids_pidof を用いてプログラムが実行中かどうか調べる。
    # 関数 __pids_pidof はプログラムが実行中であればプロセス ID を出力する。
    # 関数 __pids_pidof でも実行中のプロセスが存在しないと判断された場合は、
    # 関数 __pids_var_run の戻り値をこの関数の戻り値として使用する。
    __pids_pidof "$1" || return $RC
}


#==================================================
# 関数 status()
#
# 概要
#     プログラムの実行状態を表示する。
#
# 使い方
#     status  [-p プロセスIDファイル]  [-l ロックファイル]  プログラム名
#
# オプション
#     -p プロセスIDファイル
#         プログラムの実行状態を調べる際に読み込むプロセスIDファイルを
#         指定する。明示的に指定しない場合のデフォルトプロセス ID ファイルは
#         「/var/run/プログラムベース名.pid」。
#
#     -l ロックファイル
#         ロックファイルのベース名を指定する。明示的に指定しない場合は、
#         プログラムのベース名がロックファイルのベース名として使用される。
#         対象プログラムが実行中で無い場合に限り、ロックファイルの有無が
#         チェックされ、もしもロックファイルが残っているようであれば、その旨の
#         表示がおこなわれる。なお、ロックファイルの完全パス名は
#         「/var/lock/subsys/ロックファイルのベース名」である。
#
#     注意事項
#         -p オプションと -l オプションの両方を指定する場合は、この順番で
#         指定しなければならない。つまり、-p オプションは常に -l オプションの
#         前に指定しなければならない。
#
# 戻り値
#     0: プログラムは実行中である。
#     1: プログラムは停止しているが、プロセス ID ファイルが残っている。
#     2: プログラムは停止しているが、ロックファイルが残っている。
#     3: プログラムは停止している。
#     4: プロセス ID ファイルを読み込む権限が無いため、プログラムの
#        実行状態を判定できない。
#
# 表示例
#     myprogram (pid 12345) is running...
#     myprogram dead but pid file exists
#     myprogram dead but subsys locked
#     myprogram is stopped
#     myprogram status unknown due to insufficient privileges.
#
status() {
    # ローカル変数 base, pid, lock_file, pid_file を宣言する。
    local base pid lock_file= pid_file=

    # Test syntax.
    # 引数が一つもない場合
    if [ "$#" = 0 ] ; then
        # 使い方を表示し、戻り値 1 で関数を抜ける。
        echo $"Usage: status [-p pidfile] {program}"
        return 1
    fi

    # 第一引数が「-p」の場合、
    if [ "$1" = "-p" ]; then
        # -p の後に続く引数の値を変数 pid_file に設定する。
        pid_file=$2

        # -p とその後に続く引数を処理したので、位置パラメータから二つ取り除く。
        shift 2
    fi

    # 現在の位置パラメータの先頭の要素の値が「-l」の場合、
    if [ "$1" = "-l" ]; then
        # -l の後に続く引数の値を変数 lock_file に設定する。
        lock_file=$2

        # -l とその後に続く引数を処理したので、位置パラメータから二つ取り除く。
        shift 2
    fi

    # -p オプションと -l オプションを処理した後に残った引数をプログラム名とみなし、
    # そのベース名を変数 base に設定する。${parameter##word} という表記は、
    # 変数 parameter の値から、パターン word に前方最長一致する部分を
    # 取り除くという意味。パターン「*/」を取り除くということは、最後のスラッシュと
    # それより前にある文字を全て取り除くということ。すなわち、$1 がパス名で
    # あれば、そこからディレクトリ名部分を取り除くことになる。
    base=${1##*/}

    # First try "pidof"
    # __pids_var_run 関数を用いて、プログラムが実行中かどうかを調べる。
    # プログラムが実行中の場合、変数 pid にプロセス ID 群が格納される。
    __pids_var_run "$1" "$pid_file"

    # __pids_var_run 関数の戻り値を変数 RC に格納する。
    RC=$?

    # 明示的にプロセス ID ファイルが指定されておらず、かつ、__pids_var_run
    # 関数で調べた結果、実行中のプロセスが存在しないと判断された場合、
    if [ -z "$pid_file" -a -z $pid" ]; then
        # __pids_pidof 関数を用いて、プログラムが実行中かどうかを調べる。
        pid="$(__pids_pidof "$1")"
    fi

    #__pids_var_run 関数、もしくは __pids_pidof 関数により、プログラムの
    # 実行中プロセスが見つかった場合
    if [ -n "$pid" ]; then
        # プログラムが実行中である旨を表示し、戻り値 0 で関数を抜ける。
        echo $"${base} (pid $pid) is running..."
        return 0
    fi

    # __pids_var_run 関数の実行結果に基づいて分岐する。$RC が 3 以外の場合
    # (= プロセス ID ファイルが存在しなかった場合)を除き、プログラムの状態を
    # 表示して関数を抜ける。
    case "$RC" in
        0)
            # プログラムが実行中である旨を表示し、戻り値 0 で関数を抜ける。
            echo $"${base} (pid $pid) is running..."
            return 0
            ;;
        1)
            # プログラムは実行中ではないが、プロセス ID ファイルが残っている
            # 旨を表示し、戻り値 1 で関数を抜ける。
            echo $"${base} dead but pid file exists"
            return 1
        4)
            # プロセス ID ファイルを読み込むの権限が無いため、プログラムが
            # 実行中であるかどうかが分からない旨を表示し、戻り値 4 で関数を抜ける。
            echo $"${base} status unknown due to insufficient privileges."
            return 4
            ;;
    esac

    # -l オプションで明示的にロックファイルが指定されていない場合、
    if [ -z "${lock_file}" ]; then
        # プログラムのベース名をロックファイル名として用いる。
        lock_file=${base}
    fi

    # __pids_var_run 関数を実行したとき、プロセス ID ファイルが存在していなかった。

    # See if /var/lock/subsys/${lock_file} exists
    # もしもロックファイルが存在しているなら
    if [ -f /var/lock/subsys/${lock_file} ]; then
        # プログラムは実行中ではないがサブシステムがロックされている旨の
        # 表示をおこない、戻り値 2 で関数を抜ける。
        echo $"${base} dead but subsys locked"
        return 2
    fi

    # プログラムは実行中ではない旨の表示をおこない、戻り値 3 で関数を抜ける。
    echo $"${base} is stopped"
    return 3
}


#==================================================
# 関数 echo_success()
#
# "[  OK  ]" と表示する。表示後、カーソルの位置は行先頭に戻る。関数の戻り値は 0。
#
# 変数 BOOTUP の値が color であれば、"[  OK  ]" の出力位置と表示属性は ANSI
# エスケープシーケンスにより調整される。出力位置を設定するコマンドについては変数
# MOVE_TO_COL を、表示属性については変数 SETCOLOR_SUCCESS を参照のこと。
#
echo_success() {
    # 変数 BOOTUP の値が color であれば、カーソルの絶対位置を設定する。
    [ "$BOOTUP" = "color" ] && $MOVE_TO_COL

    # 左角括弧 "[" を表示する。
    echo -n "["

    # 以降に出力する文字の表示属性等を変更する。
    [ "$BOOTUP" = "color" ] && $SETCOLOR_SUCCESS

    # "  OK  " を表示する。
    echo -n $"  OK  "

    # 以降に出力する文字の表示属性を元に戻す。
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL

    # 右角括弧 "]" を表示する。
    echo -n "]"

    # カーソルの位置を行の先頭に戻す。
    echo -ne "\r"
    return 0
}


#==================================================
# 関数 echo_failure()
#
# "[FAILED]" と表示する。表示後、カーソルの位置は行先頭に戻る。関数の戻り値は 1。
#
# 変数 BOOTUP の値が color であれば、"[FAILED]" の出力位置と表示属性は ANSI
# エスケープシーケンスにより調整される。出力位置を設定するコマンドについては変数
# MOVE_TO_COL を、表示属性については変数 SETCOLOR_FAILURE を参照のこと。
#
echo_failure() {
    # 変数 BOOTUP の値が color であれば、カーソルの絶対位置を設定する。
    [ "$BOOTUP" = "color" ] && $MOVE_TO_COL

    # 左角括弧 "[" を表示する。
    echo -n "["

    # 以降に出力する文字の表示属性等を変更する。
    [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE

    # "FAILED" を表示する。
    echo -n $"FAILED"

    # 以降に出力する文字の表示属性を元に戻す。
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL

    # 右角括弧 "]" を表示する。
    echo -n "]"

    # カーソルの位置を行の先頭に戻す。
    echo -ne "\r"
    return 1
}


#==================================================
# 関数 echo_passed()
#
# "[PASSED]" と表示する。表示後、カーソルの位置は行先頭に戻る。関数の戻り値は 1。
#
# 変数 BOOTUP の値が color であれば、"[PASSED]" の出力位置と表示属性は ANSI
# エスケープシーケンスにより調整される。出力位置を設定するコマンドについては変数
# MOVE_TO_COL を、表示属性については変数 SETCOLOR_WARNING を参照のこと。
#
echo_passed() {
    # 変数 BOOTUP の値が color であれば、カーソルの絶対位置を設定する。
    [ "$BOOTUP" = "color" ] && $MOVE_TO_COL

    # 左角括弧 "[" を表示する。
    echo -n "["

    # 以降に出力する文字の表示属性等を変更する。
    [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING

    # "PASSED" を表示する。
    echo -n $"PASSED"

    # 以降に出力する文字の表示属性を元に戻す。
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL

    # 右角括弧 "]" を表示する。
    echo -n "]"

    # カーソルの位置を行の先頭に戻す。
    echo -ne "\r"
    return 1
}


#==================================================
# 関数 echo_warning()
#
# "[WARNING]" と表示する。表示後、カーソルの位置は行先頭に戻る。関数の戻り値は 1。
#
# 変数 BOOTUP の値が color であれば、"[WARNING]" の出力位置と表示属性は ANSI
# エスケープシーケンスにより調整される。出力位置を設定するコマンドについては変数
# MOVE_TO_COL を、表示属性については変数 SETCOLOR_WARNING を参照のこと。
#
echo_warning() {
    # 変数 BOOTUP の値が color であれば、カーソルの絶対位置を設定する。
    [ "$BOOTUP" = "color" ] && $MOVE_TO_COL

    # 左角括弧 "[" を表示する。
    echo -n "["

    # 以降に出力する文字の表示属性等を変更する。
    [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING

    # "WARNING" を表示する。
    echo -n $"WARNING"

    # 以降に出力する文字の表示属性を元に戻す。
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL

    # 右角括弧 "]" を表示する。
    echo -n "]"

    # カーソルの位置を行の先頭に戻す。
    echo -ne "\r"
    return 1
}


#==================================================
# 関数 update_boot_state()
#
# /bin/plymouth が実行可能であれば、次のコマンドを実行する。
#
#     /bin/plymouth --update="第一引数の値"
#
# 戻り値は常に 0。
#
# Inform the graphical boot of our current state
update_boot_state() {
    if [ -x /bin/plymouth ]; then
        /bin/plymouth --update="$1"
    fi
    return 0
}


#==================================================
# 関数 success()
#
# 変数 BOOTUP の値が verbose でなく、かつ、変数 LSB の値が空であれば、
# 関数 echo_success を実行する。
#
# この関数の戻り値は 0 である。
#
# Log that something succeeded
success() {
    # 変数 BOOTUP の値が verbose でなく、かつ、変数 LSB の値が空であれば、
    # 関数 echo_success を実行する。
    [ "$BOOTUP" != "verbose" -a -z "${LSB:-}" ] && echo_success
    return 0
}


#==================================================
# 関数 failure()
#
# 変数 BOOTUP の値が verbose でなく、かつ、変数 LSB の値が空であれば、
# 関数 echo_failure を実行する。その後、/bin/plymouth が実行可能であれば、
# /bin/plymouth --details を実行する。
#
# この関数の戻り値は、この関数をコールする直前に実行したコマンドの終了
# コードとなる。
#
# Log that something failed
failure() {
    # 直前に実行したコマンドの終了コード ($?) をローカル変数 rc に設定する。
    local rc=$?

    # 変数 BOOTUP の値が verbose でなく、かつ、変数 LSB の値が空であれば、
    # 関数 echo_failure を実行する。
    [ "$BOOTUP" != "verbose" -a -z "${LSB:-}" ] && echo_failure

    # /bin/plymouth が実行可能であれば、/bin/plymouth --details を実行する。
    [ -x /bin/plymouth ] && /bin/plymouth --details

    # この関数をコールする直前に実行したコマンドの終了コードを、この関数の
    # 終了コードとする。
    return $rc
}


#==================================================
# 関数 passed()
#
# 変数 BOOTUP の値が verbose でなく、かつ、変数 LSB の値が空であれば、
# 関数 echo_passed を実行する。
#
# この関数の戻り値は、この関数をコールする直前に実行したコマンドの終了
# コードとなる。
#
# Log that something passed, but may have had errors. Useful for fsck
passed() {
    # 直前に実行したコマンドの終了コード ($?) をローカル変数 rc に設定する。
    local rc=$?

    # 変数 BOOTUP の値が verbose でなく、かつ、変数 LSB の値が空であれば、
    # 関数 echo_passed を実行する。
    [ "$BOOTUP" != "verbose" -a -z "${LSB:-}" ] && echo_passed

    # この関数をコールする直前に実行したコマンドの終了コードを、この関数の
    # 終了コードとする。
    return $rc
}


#==================================================
# 関数 warning()
#
# 変数 BOOTUP の値が verbose でなく、かつ、変数 LSB の値が空であれば、
# 関数 echo_warning を実行する。
#
# この関数の戻り値は、この関数をコールする直前に実行したコマンドの終了
# コードとなる。
#
# Log a warning
warning() {
    # 直前に実行したコマンドの終了コード ($?) をローカル変数 rc に設定する。
    local rc=$?

    # 変数 BOOTUP の値が verbose でなく、かつ、変数 LSB の値が空であれば、
    # 関数 echo_warning を実行する。
    [ "$BOOTUP" != "verbose" -a -z "${LSB:-}" ] && echo_warning

    # この関数をコールする直前に実行したコマンドの終了コードを、この関数の
    # 終了コードとする。
    return $rc
}


#==================================================
# 関数 action()
#
# 第二引数で指定されたコマンドを実行し、実行結果に対して [OK] もしくは [FAILED] の
# 表示をおこなう。
#
# 使い方
#     action  ラベル  コマンド  [コマンド引数...]
#
# 動作詳細
#     1. 第一引数で指定されたラベルを改行無しで表示する。
#     2. 第二引数で指定されたコマンドを実行し、その終了を待つ。第三引数以降は、
#         実行するコマンドの引数として、当コマンドに渡される。
#     3. 実行したコマンドが成功終了した場合(終了コードが 0 の場合)は [OK] を、
#         そうでなければ [FAILED] を表示する。
#     4. 実行したコマンドの終了コードをこの関数の終了コードとして返す。
#
# 使用例
#     下記のコマンドを実行すると、"Sleeping..." と表示され、その 3 秒後に [OK] が
#     出力される。
#
#         (. /etc/init.d/functions; action "Sleeping..." sleep 3)
#
# Run some action. Log its output.
action() {
    # ローカル変数を宣言する。
    local STRING rc

    # 第一引数の値(ラベル)を変数 STRING に設定し、改行無しで表示する。
    STRING=$1
    echo -n "$STRING "

    # 第一引数(ラベル)を消費したので、位置パラメータ群から取り除く。
    shift

    # 現在の位置パラメータ群 (すなわち、この関数に渡された第二引数および
    # それ以降の引数群) がコマンドライン文字列を構成しているものとみなして実行し、
    # 実行結果を 「ラベル [OK]」 もしくは 「ラベル [FAILED]」 という形式で表示する。
    # "$@" という記述は、"$1" "$2" "$3" ... という形に展開される。
    "$@" && success $"$STRING" || failure $"$STRING"

    # 直前に実行したコマンドの終了コードを変数 rc に設定する。
    rc=$?

    # 改行を出力する。
    echo

    # 第二引数で指定されたコマンドの終了コードをこの関数の終了コードとする。
    return $rc
}


#==================================================
# 関数 strstr()
#
# 第一引数に渡された文字列に第二引数で渡された文字列が含まれていれば
# 戻り値 0、含まれていなければ戻り値 1 を返す。
# # returns OK if $1 contains $2
strstr() {
    # "${parameter#word}" という書き方は、word で指定されたパターンと
    # 前方一致する部分を取り除いた値となる (bash の man ページを参照のこと)。
    # このため、"${1#*$2*}" は、第一引数の値から、「*第二引数の値*」という
    # パターンに前方一致する部分を取り除いた値ということになる。もし、
    # 「*第二引数の値*」というパターンを取り除いた値が第一引数と同じで
    # あれば、それはすなわち、第一引数に「*第二引数の値*」というパターンが
    # 含まれていないということを意味する。
    [ "${1#*$2*}" = "$1" ] && return 1
    return 0
}


#==================================================
# 関数 confirm()
#
# 第一引数で指定されたサービス名を開始するかを尋ねるプロンプト
# 「Start service サービス名 (Y)es/(N)o/(C)ontinue? [Y]」を表示する。
# 有効な入力値が得られれば、関数を抜ける。
#
# 戻り値
#     0: y もしくは Y が入力された (または何も入力せずにエンターキーが押された)。
#     1: n もしくは N が入力された。 
#     2: c もしくは C が入力された。
#
# 副作用
#     * /bin/plymouth が実行可能であれば、プロンプトを表示する前に
#       「/bin/plymouth --hide-splash」を実行する。
#     * 入力された値が c もしくは C の場合、/var/run/confirm ファイルが
#       削除される。
#     * 入力された値が c もしくは C の場合で、かつ、/bin/plymouth が
#       実行可能であれば、「/bin/plymouth --show-splash」を実行する。
#
# Confirm whether we really want to run this service
confirm() {
    # /bin/plymouth が実行可能であれば、「/bin/plymouth --hide-splash」を
    # 実行する。
    [ -x /bin/plymouth ] && /bin/plymouth --hide-splash

    while : ; do
        # サービスを開始するかどうかを尋ねるプロンプトを表示する。
        # echo の -n オプションにより、末尾に改行は表示されない。
        echo -n $"Start service $1 (Y)es/(N)o/(C)ontinue? [Y] "

        # 入力を読み取り、その値を変数 answer に代入する。
        read answer

        # 読み取った値が、y もしくは Y であれば、もしくは空であれば、
        if strstr $"yY" "$answer" || [ "$answer" = "" ] ; then
            # 戻り値 0 でこの関数から抜ける。
            return 0

        # 読み取った値が、c もしくは C であれば、
        elif strstr $"cC" "$answer" ; then
            # /var/run/confirm ファイルを削除する。
            rm -f /var/run/confirm

            # /bin/plymouth コマンドが実行可能であれば、
            # 「/bin/plymouth --show-splash」を実行する。
            [ -x /bin/plymouth ] && /bin/plymouth --show-splash

            # 戻り値 2 でこの関数から抜ける。
            return 2

        # 読み取った値が、n もしくは N であれば、
        elif strstr $"nN" "$answer" ; then

            # 戻り値 1 でこの関数から抜ける。
            return 1
        fi
    done
}


#==================================================
# 関数 get_numeric_dev()
#
# 概要
#     デバイスファイルのメジャー番号とマイナー番号を「メジャー番号:マイナー番号」
#     という書式で出力する。
#
# 使い方
#     get_numeric_dev    フォーマット    デバイスファイル名
#
# 引数
#     「フォーマット」には、hex もしくは任意の文字列を指定する。hex が指定された
#     場合は、メジャー番号とマイナー番号が 16 進数で表示される。それ以外の値の
#     場合は、10 進数で表示される。
#
# 使用例
#     get_numeric_dev hex /dev/null
#     get_numeric_dev dec /dev/zero
#
# resolve a device node to its major:minor numbers in decimal or hex
get_numeric_dev() {
# コマンド群をサブシェルで実行するため、括弧で囲む。
# 末尾に「2> /dev/null」と書いてあるので、標準エラー出力は表示されない。
(
    # 変数 fmt のデフォルト値を「%d:%d」に設定する。
    fmt="%d:%d"

    # 関数に渡された第一引数が hex という文字列であった場合、
    if [ "$1" == "hex" ]; then        # 変数 fmt の値を「%x:%x」へと再設定する。
        fmt="%x:%x"
    fi

    # 第二引数で渡されたファイル名を引数にして「ls -lH ファイル名」を実行し、
    # その出力を awk コマンドで処理する。-H オプションは、シンボリックリンクを
    # たどることを指定するもの (= --dereference-command-line)。
    #
    # awk コマンドでは、入力内の五番目と六番目のカラムだけ処理対象としている。
    # 通常ファイルに対して ls -l を実行すると、五番目と六番目にはファイルの
    # サイズと日付が表示されるが、/dev ディレクトリ内にあるようなデバイスファイルに
    # 対して ls -l を実行すると、五番目と六番目には、そのデバイスのメジャー番号と
    # マイナー番号が表示される。例えば、/dev/null に対して ls -lH を実行すると
    # 次のように出力される。
    #
    #    $ LANG=C ls -lH /dev/null
    #    crw-rw-rw- 1 root root 1, 3 Aug  7 13:31 /dev/null
    #
    # 五番目のメジャー番号は末尾にカンマがついているので、awk の処理では
    # sub(/,/, "", $5) によりカンマを取り除いている。その後、printf により、変数
    # fmt に設定されている出力書式を用いてメジャー番号とマイナー番号を
    # 出力している。
    ls -lH "$2" | awk '{ sub(/,/, "", $5); printf("'"$fmt"'", $5, $6); }'
) 2>/dev/null
}


#==================================================
# 関数 is_ignored_file()
#
# 第一引数の値が次のパターンのいずれかにマッチすれば 0 を返す。
# そうでなければ 1 を返す。
#
#    *~
#    *.bak
#    *.orig
#    *.rpmnew
#    *.rpmorig
#    *.rpmsave
#
# Check whether file $1 is a backup or rpm-generated file and should be ignored
is_ignored_file() {
    case "$1" in
        *~ | *.bak | *.orig | *.rpmnew | *.rpmorig | *.rpmsave)
            return 0
            ;;
    esac
    return 1
}


#==================================================
# 関数 is_true()
#
# 第一引数の値が t, y, true, yes のいずれかの場合(文字の大小は問わない)、
# 0 を返す。そうでなければ 1 を返す。
#
# Evaluate shvar-style booleans
is_true() {
    case "$1" in
        [tT] | [yY] | [yY][eE][sS] | [tT][rR][uU][eE])
            return 0
            ;;
    esac
    return 1
}


#==================================================
# 関数 is_false()
#
# 第一引数の値が f, n, false, no のいずれかの場合(文字の大小は問わない)、
# 0 を返す。そうでなければ 1 を返す。
#
# Evaluate shvar-style booleans
is_false() {
    case "$1" in
        [fF] | [nN] | [nN][oO] | [fF][aA][lL][sS][eE])
            return 0
            ;;
    esac
    return 1
}


#==================================================
# 関数 apply_sysctl()
#
# /etc/sysctl.conf ファイルと /etc/sysctl.d ディレクトリ内の通常ファイル群に
# 対して順次 「sysctl -e -p ファイル」 コマンドを実行する。但し、/etc/sysctl.d
# ディレクトリ内の *~, *.bak, *.orig, *.rpmnew, *.rpmorig, *.rpmsave ファイル
# 群は対象としない。
#
# Apply sysctl settings, including files in /etc/sysctl.d
apply_sysctl() {
    # /etc/sysctl.conf の内容に基づいてカーネルパラメータを設定する。
    # -e オプションは認識できないキーを無視するためのオプション。
    sysctl -e -p /etc/sysctl.conf >/dev/null 2>&1

    # /etc/sysctl.d ディレクトリに存在するファイルを一つずつ処理するループ。
    for file in /etc/sysctl.d/* ; do
        # 関数 is_ignored_file() を用いてファイルが無視すべきものであるかどうかを調べる。
        is_ignored_file "$file" && continue

        # ファイルが通常ファイルであれば、その内容を sysctl コマンドでロードする。
        test -f "$file" && sysctl -e -p "$file" >/dev/null 2>&1
    done
}


#==================================================
# 関数 key_is_random()
#
# 第一引数の値が次のいずれかであれば真。
#
#    /dev/urandom
#    /dev/hw_random
#    /dev/random
#
key_is_random() {
    [ "$1" = "/dev/urandom" -o "$1" = "/dev/hw_random" \
        -o "$1" = "/dev/random" ]
}


#==================================================
# 関数 find_crypto_mount_point()
#
# 説明
#     指定された暗号化デバイスのマウントポイントを出力する。
#     指定されたデバイスに関する記述が /etc/fstab 内になければ、
#     何も出力しない。
#
# 使い方
#     find_crypto_mount_point  デバイス名
#
# 引数
#     デバイス名
#         /dev/mapper/{name} の {name} にあたる部分。
#
find_crypto_mount_point() {
    # ローカル変数を宣言する。
    local fs_spec fs_file fs_vfstype remaining_fields
    local fs

    # /etc/fstab ファイルの内容を一行ずつ処理するループ。行ごとに、
    # 第一フィールド の値を変数 fs_spec に、第二フィールドの値を
    # 変数 fs_file に、残りのフィールド群の値を変数 remaining_fields に
    # 代入しながらループを回す。
    while read fs_spec fs_file remaining_fields; do
        # 第一フィールドの値が「/dev/mapper/$1」に合致した場合。
        # ここで $1 の値は、関数 find_crypto_mount_point の第一引数
        # として渡されたデバイス名。
        if [ "$fs_spec" = "/dev/mapper/$1" ]; then
            # 当該デバイスのマウントポイントを出力して関数の処理を終える。
            echo $fs_file
            break;
        fi
    done < /etc/fstab
}


#==================================================
# 関数 init_crypto()
#
# TBW
#
# Because of a chicken/egg problem, init_crypto must be run twice.  /var may be
# encrypted but /var/lib/random-seed is needed to initialize swap.
init_crypto() {
    local have_random dst src key opt mode owner params makeswap skip arg opt
    local param value rc ret mke2fs mdir prompt mount_point

    ret=0
    have_random=$1
    while read dst src key opt; do
        [ -z "$dst" -o "${dst#\#}" != "$dst" ] && continue
        [ -b "/dev/mapper/$dst" ] && continue
        if [ "$have_random" = 0 ] && key_is_random "$key"; then
            continue
        fi
        if [ -n "$key" -a "x$key" != "xnone" ]; then
            if test -e "$key" ; then
                owner=$(ls -l $key | (read a b owner rest; echo $owner))
                if ! key_is_random "$key"; then
                    mode=$(ls -l "$key" | cut -c 5-10)
                    if [ "$mode" != "------" ]; then
                        echo $"INSECURE MODE FOR $key"
                    fi
                fi
                if [ "$owner" != root ]; then
                    echo $"INSECURE OWNER FOR $key"
                fi
            else
                echo $"Key file for $dst not found, skipping"
                ret=1
                continue
            fi
        else
            key=""
        fi

        params=""
        makeswap=""
        mke2fs=""
        skip=""
        # Parse the src field for UUID= and convert to real device names
        if [ "${src%%=*}" == "UUID" ]; then
            src=$(/sbin/blkid -t "$src" -l -o device)
        elif [ "${src/^\/dev\/disk\/by-uuid\/}" != "$src" ]; then
            src=$(__readlink $src)
        fi
        # Is it a block device?
        [ -b "$src" ] || continue
        # Is it already a device mapper slave? (this is gross)
        devesc=${src##/dev/}
        devesc=${devesc//\//!}
        for d in /sys/block/dm-*/slaves ; do
            [ -e $d/$devesc ] && continue 2
        done
        # Parse the optinos field, convert to cryptsetup parameters and
        # contruct the command line
        while [ -n "$opt" ]; do
            arg=${opt%%,*}
            opt=${opt##$arg}
            opt=${opt##,}
            param=${arg%%=*}
            value=${arg##$param=}

            case "$param" in
            cipher)
                params="$params -c $value"
                if [ -z "$value" ]; then
                    echo $"$dst: no value for cipher option, skipping"
                    skip="yes"
                fi
                ;;
            size)
                params="$params -c $value"
                if [ -z "$value" ]; then
                    echo $"$dst: no value for size option, skipping"
                    skip="yes"
                fi
                ;;
            hash)
                params="$params -h $value"
                if [ -z "$value" ]; then
                    echo $"$dst: no value for hash option, skipping"
                    skip="yes"
                fi
                ;;
            verify)
                params="$params -y"
                ;;
            swap)
                makeswap=yes
                ;;
            tmp)
                mke2fs=yes
            esac
        done

        if [ "$skip" = "yes" ]; then
            ret=1
            continue
        fi

        if [ -z "$makeswap" ] && cryptsetup isLuks "$src" 2>/dev/null ; then
            if key_is_random "$key"; then
                echo $"$dst: LUKS requires non-random key, skipping"
                ret=1
                continue
            fi

            if [ -n "$params" ]; then
                echo "$dst: options are invalid for LUKS partitions," \
                    "ignoring them"
            fi

            if [ -n "$key" ]; then
                /sbin/cryptsetup -d $key luksOpen "$src" "$dst" <&1 2>/dev/null && success || failure
                rc=$?
            else
                mount_point="$(find_crypto_mount_point $dst)"
                [ -n "$mount_point" ] || mount_point=${src##*/}
                prompt=$(printf $"%s is password protected" "$mount_point")
                plymouth ask-for-password --prompt "$prompt" --command="/sbin/cryptsetup luksOpen -T1 $src $dst" <&1
                rc=$?
            fi
        else
            [ -z "$key" ] && plymouth --hide-splash
            /sbin/cryptsetup $params ${key:+-d $key} create "$dst" "$src" <&1 2>/dev/null && success || failure
            rc=$?
            [ -z "$key" ] && plymouth --show-splash
        fi

        if [ $rc -ne 0 ]; then
            ret=1
            continue
        fi

        if [ -b "/dev/mapper/$dst" ]; then
            if [ "$makeswap" = "yes" ]; then
                mkswap "/dev/mapper/$dst" 2>/dev/null >/dev/null
            fi

            if [ "$mke2fs" = "yes" ]; then
                if mke2fs "/dev/mapper/$dst" 2>/dev/null >/dev/null \
                    && mdir=$(mktemp -d /tmp/mountXXXXXX); then
                    mount "/dev/mapper/$dst" "$mdir" && chmod 1777 "$mdir"
                    umount "$mdir"
                    rmdir "$mdir"
                fi
            fi
        fi
    done < /etc/crypttab
    return $ret
}


# A sed expression to filter out the files that is_ignored_file recognizes
__sed_discard_ignored_files='/\(~\|\.bak\|\.orig\|\.rpmnew\|\.rpmorig\|\.rpmsave\)$/d'