GRUB2のメニューに再起動と停止を追加する(debian squeeze amd64)


2011/02/15

grub2には再起動のコマンドと停止のコマンドがある。
メニューからこれを選択できるようにする。

grub2はgrubと違い設定ファイルを直接修正しない。
設定ファイルを生成するためのファイルを修正する。
そしてそのファイルからコマンドを使用し設定ファイルを生成する。
/etc/default/grubと/etc/grub.d/に置いてあるシェルスクリプト群がそれにあたる。
/etc/default/grubはタイムアウト等の設定を行う。
メニューの設定は/etc/grub.d/に置いてあるスクリプトが行う。
(/etc/default/grubに書いた設定も実際は環境変数として設定され/etc/grub.d/00_header等が解釈し設定ファイルに書き出す)
/etc/grub.d/にあるシェルスクリプトは数が小さいほうから順に実行される。
そして実際の設定ファイルである/boot/grub/grub.cfgを生成する。
メニューを追加するためには40_customにメニューエントリーを追加する。
あるいは50_myentryの様なスクリプトファイルを追加しその中に追加するメニューを書く。
しかし今回はどちらの方法もとらない。
custom.cfgを使用する。
この設定は41_customでgrub.cfgに書き出される。
この設定は起動時にgrub.cfgからcustom.cfgを呼び出す。
このcustom.cfgを使用してメニューを追加する。
理由はcustom.cfgを使用する方法を検索したが日本語で書いてある使用方法が見つからなかったから。
それにこの方法ならばメニューを追加するごとにgrub.cfgを生成しなおす必要がない。
custom.cfgはgrub.cfgと同じディレクトリに置く。

custom.cfgを作成する。

$ sudo nano /boot/grub/custom.cfg

以下の内容を書く


menuentry 'halt' {
        halt
}

menuentry 'reboot' {
        reboot
}

これで次の起動からgrub2のメニューにhaltとrebootが追加される。
haltは停止、rebootは再起動のコマンドになる。


2011/02/16

昨日記述した内容は途中で方針を変更してcustom.cfgを使用する方法にしたために前後の文のつながりがおかしい。
シェルスクリプトを自分で追加する方法も紹介する。
停止用と再起動用の2つのスクリプトを追加する。
1つのスクリプトにメニューを1つしか記述できないわけではない。
1つのスクリプトにメニューを2つ記述してもよい。

スクリプトを置くためのフォルダに移動する。

$ cd /etc/grub.d

40_customをコピーし雛形を作る。

$ sudo cp 40_custom 51_halt
$ sudo cp 40_custom 52_reboot

停止用のスクリプトを編集する。

$ sudo nano 51_halt

以下の内容を追加する。
コメント行は削除してしまってもよい。
(削除しないとコメントもgrub.cfgに追加される。)


menuentry 'halt' {
        halt
}

再起動用のスクリプトを編集する。

$ sudo nano 52_reboot

以下の内容を追加する。


menuentry 'reboot' {
        reboot
}

/boot/grub/grub.cfgを生成する。

$ sudo update-grub

エラーが出なければ生成成功。

結果を確認する。

$ cat /boot/grub/grub.cfg

最後のほうに追加した内容が反映している。


### BEGIN /etc/grub.d/51_halt ###
menuentry 'halt' {
        halt
}
### END /etc/grub.d/51_halt ###

### BEGIN /etc/grub.d/52_reboot ###
menuentry 'reboot' {
        reboot
}
### END /etc/grub.d/52_reboot ###

次回の起動からgrub2のメニューにhaltとrebootが追加される。


2011/03/09

grub.cfg生成の仕組み

grub.cfgがどの様に生成されるかを確認する。
まずは簡単な40_customから。

40_customの仕組み

40_customはシェルスクリプトなので実行できる。
実行してみる。

$ /etc/grub.d/40_custom

ファイルを変更していなければ以下のように表示される。


# This file provides an easy way to add custom menu entries.  Simply type the
# menu entries you want to add after this comment.  Be careful not to change
# the 'exec tail' line above.

grub.cfgの中にこれと一致する記述がある。


### BEGIN /etc/grub.d/40_custom ###
# This file provides an easy way to add custom menu entries.  Simply type the
# menu entries you want to add after this comment.  Be careful not to change
# the 'exec tail' line above.
### END /etc/grub.d/40_custom ###

### BEGIN /etc/grub.d/40_custom ###と### END /etc/grub.d/40_custom ###に囲まれた部分が一致しているのが分かる。
grub.cfgの中に他にも### BEGIN スクリプト ###と### END スクリプト ###で囲まれた記述がある。
/etc/grub.d00_headerや/etc/grub.d05_debian_theme等だ。
grub.cfgは各スクリプトの出力を### BEGIN スクリプト ###と### END スクリプト ###の間に記述した内容になっている。
しかしコマンドラインから実行したものと完全に一致するものは少ない。
これは環境変数を使用してるためで変数が設定されていない場合と設定されている場合で出力内容が違うからだ。
40_customは環境変数を使用していないのでスクリプトの出力とgrub.cfgの該当部分の内容が一致する。
次は40_customの内容を見る。

$ cat /etc/grub.d/40_custom

以下のように表示される。


#!/bin/sh
exec tail -n +3 $0
# This file provides an easy way to add custom menu entries.  Simply type the
# menu entries you want to add after this comment.  Be careful not to change
# the 'exec tail' line above.

1行目がこのファイルがシェルスクリプトであることを示している。
このファイルを引数として/bin/shを実行するという意味だ。
試してみる。

$ /bin/sh /etc/grub.d/40_custom

出力内容は40_customを実行したときと同じになる。
2行目のexecは引数として渡されるコマンドに実行制御を移すシェルの組み込みコマンドだ。
実行をtailに移す。
3行目以降が実行されることはない。
2行目の最後にある$0はシェルのパラメータで40_custom自身を意味する。
このスクリプトは結局"tail -n +3 /etc/grub.d/40_custom"の意味になる。
実行してみる。

$ tail -n +3 /etc/grub.d/40_custom

やはり40_customを実行したときの内容と同じになる。
"tail -n +3 ファイル"はファイルの3行目より後ろを表示するという意味だ。
つまりこのスクリプトの3行目以降に記述された内容がそのままgrub.cfgに反映する。

update-grub2の仕組み

/etc/grub.d/にあるシェルスクリプトからgrub.cfgを構築するコマンドはupdate-grubかupdate-grub2だ。
update-grub2の中身を確認する。

$ cat /usr/sbin/update-grub2

以下のように表示される。


#!/bin/sh
set -e
exec update-grub "$@"

2行目のset -eはエラーがでたら終了するという意味になる。
3行目はupdate-grub2を実行するときにつけた引数はすべてつけてupdate-grubを実行するという意味になる。
つまりupdate-grubを実行するのと同じことになる。

update-grubの仕組み

update-grub2の中身を確認する。

以下のように表示される。


#!/bin/sh
set -e
exec grub-mkconfig -o /boot/grub/grub.cfg "$@"

grub-mkconfigを実行している。
grub-mkconfigの使い方を確認してみる。

$ sudo grub-mkconfig --help

以下のように表示される。


Usage: grub-mkconfig [OPTION]
Generate a grub config file

  -o, --output=FILE       output generated config to FILE [default=stdout]
  -h, --help              print this message and exit
  -v, --version           print the version information and exit

Report bugs to <bug-grub@gnu.org>.

-oオプションは出力ファイルを指定するオプションだと分かる。
/boot/grub/grub.cfgを出力ファイルに指定している。
出力ファイルを指定しなければどうなるか確認してみる。

$ sudo grub-mkconfig

少し違うがほとんどgrub.cfgと同じ内容になる。
違う部分は生成する際に出力されるメッセージが間に挟まっているからだ。
update-grubを実行することはgrub-mkconfig -o /boot/grub/grub.cfgを実行するのと同じになる。

2011/03/10

grub-mkconfigの仕組み

grub-mkconfigは今までのものより大きいので駆け足で行く。
先頭のほう変数の定義になっている。


transform="s,x,x,"

prefix=/usr
exec_prefix=${prefix}
sbindir=${exec_prefix}/sbin
libdir=${exec_prefix}/lib
sysconfdir=/etc
PACKAGE_NAME=GRUB
PACKAGE_VERSION=1.98+20100804-14
host_os=linux-gnu
datarootdir=${prefix}/share
datadir=${datarootdir}
pkgdatadir=${datadir}/`echo grub | sed "${transform}"`
grub_cfg=""
grub_mkconfig_dir=${sysconfdir}/grub.d

self=`basename $0`

grub_mkdevicemap=${sbindir}/`echo grub-mkdevicemap | sed ${transform}`
grub_probe=${sbindir}/`echo grub-probe | sed ${transform}`

grub_cfgがこの時点では空文字列になっている。
grub_mkconfig_dirは/etc/grub.dを示している。


# Usage: usage
# Print the usage.
usage () {
    cat <EOF
Usage: $self [OPTION]
Generate a grub config file

  -o, --output=FILE       output generated config to FILE [default=stdout]
  -h, --help              print this message and exit
  -v, --version           print the version information and exit

Report bugs to <bug-grub@gnu.org>.
EOF
}

次にusage関数が定義されている。
この関数は呼び出されるとUsageを出力する。
--helpオプションを付けて実行すると呼び出される。


argument () {
  opt=$1
  shift

  if test $# -eq 0; then
      echo "$0: option requires an argument -- '$opt'" 1<&2
      exit 1
  fi
  echo $1
}

argument関数が定義されている。
ここだけ見るとわからないが出力ファイル名を抜き出すために存在する。


# Check the arguments.
while test $# -gt 0
do
    option=$1
    shift

    case "$option" in
    -h | --help)
        usage
        exit 0 ;;
    -v | --version)
        echo "$self (${PACKAGE_NAME}) ${PACKAGE_VERSION}"
        exit 0 ;;
    -o | --output)
        grub_cfg=`argument $option "$@"`; shift;;
    --output=*)
        grub_cfg=`echo "$option" | sed 's/--output=//'`
        ;;
    -*)
        echo "Unrecognized option \`$option'" 1<&2
        usage
        exit 1
        ;;
    # Explicitly ignore non-option arguments, for compatibility.
    esac
done

変数の定義の後はここが実行される。
オプションによって処理を振り分けている。
-h、--helpオプションはusage関数を呼び出し関数から戻ると終了する。
-v、--versionオプションはバージョンを表示する。
PACKAGE_NAMEもPACKAGE_VERSIONも変数の定義で代入されていた。
selfも代入されていたがこちらはコマンドの実行結果が代入される形になっていた。
basename $0はbasename /usr/sbin/grub-mkconfigを意味しディレクトリ部分を取り除いたファイル名のみを出力する。
つまりselfにはgrub-mkconfigが代入されている。
--outputオプションにはファイル名の指定方法が2種類ありことがわかる。
=でオプションとファイル名をつなげる方法と、オプションとファイル名の間を空白であける方法だ。
-oオプションはオプションとファイル名の間を空白であける方法のみ使用できる。
--output=ファイル名の形式は--output=が取り除かれた部分がgrub_cfgに代入される。
オプションとファイル名の間を空白であける方法はargument関数の出力が代入される。
argument関数は二つ目の引数を出力する。
一つ目の引数がオプション自体で二つ目の引数がファイル名になる。
grub-mkconfig -o /boot/grub/grub.cfgの場合はgrub_cfgには/boot/grub/grub.cfgが代入される。
想定外のオプションが指定された場合はusageを表示して終了する。


. ${libdir}/grub/grub-mkconfig_lib

/usr/lib/grub/grub/grub-mkconfig_libが読み込まれる。 grub/grub-mkconfig_libでは関数が定義されている。


case "$host_os" in
netbsd* | openbsd*)
    # Because /boot is used for the boot block in NetBSD and OpenBSD, use /grub
    # instead of /boot/grub.
    GRUB_PREFIX=`echo /grub | sed ${transform}`
    ;;
*)
    # Use /boot/grub by default.
    GRUB_PREFIX=`echo /boot/grub | sed ${transform}`
    ;;
esac

host_osはlinux-gnuなのでGRUB_PREFIXには/boot/grubが代入される。


if [ "x$EUID" = "x" ] ; then
  EUID=`id -u`
fi

if [ "$EUID" != 0 ] ; then
  root=f
  case "`uname 2>/dev/null`" in
    CYGWIN*)
      # Cygwin: Assume root if member of admin group
      for g in `id -G 2>/dev/null` ; do
        case $g in
          0|544) root=t ;;
        esac
      done ;;
  esac
  if [ $root != t ] ; then
    echo "$self: You must run this as root" >&2
    exit 1
  fi
fi

実行しているのがルートかチェックしている。
ルートでなければ終了する。


set $grub_mkdevicemap dummy
if test -f "$1"; then
    :
else
    echo "$1: Not found." 1>&2
    exit 1
fi

/usr/sbin/grub-mkdevicemapがあるかチェックしている。
なければ終了する。


set $grub_probe dummy
if test -f "$1"; then
    :
else
    echo "$1: Not found." 1>&2
    exit 1
fi

/usr/sbin/grub_probeがあるかチェックしている。
なければ終了する。


mkdir -p ${GRUB_PREFIX}

/boot/grubデイレクトリがなければ作成する。


if test -e ${GRUB_PREFIX}/device.map ; then : ; else
  ${grub_mkdevicemap}
fi

/boot/grub/device.mapがなければ/usr/sbin/grub-mkdevicemapを実行し/boot/grub/device.mapを作成する。


# Device containing our userland.  Typically used for root= parameter.
GRUB_DEVICE="`${grub_probe} --target=device /`"
GRUB_DEVICE_UUID="`${grub_probe} --device ${GRUB_DEVICE} --target=fs_uuid 2> /d$

# Device containing our /boot partition.  Usually the same as GRUB_DEVICE.
GRUB_DEVICE_BOOT="`${grub_probe} --target=device /boot`"
GRUB_DEVICE_BOOT_UUID="`${grub_probe} --device ${GRUB_DEVICE_BOOT} --target=fs_$

# Filesystem for the device containing our userland.  Used for stuff like
# choosing Hurd filesystem module.
GRUB_FS="`${grub_probe} --target=fs / 2> /dev/null || echo unknown`

/と/bootのデバイスとUUIDの変数を/usr/sbin/grub-probeを使用して設定する。
ほかのファイルシステムが含まれているかもチェックしている。


if test -f ${sysconfdir}/default/grub ; then
  . ${sysconfdir}/default/grub
fi

/etc/default/grubを読み込んでいる。

grub2のターミナルの設定がある。
詳細は省略。


# These are defined in this script, export them here so that user can
# override them.
export GRUB_DEVICE \
  GRUB_DEVICE_UUID \
  GRUB_DEVICE_BOOT \
  GRUB_DEVICE_BOOT_UUID \
  GRUB_FS \
  GRUB_FONT_PATH \
  GRUB_PRELOAD_MODULES \
  GRUB_PREFIX

# These are optional, user-defined variables.
export GRUB_DEFAULT \
  GRUB_HIDDEN_TIMEOUT \
  GRUB_HIDDEN_TIMEOUT_QUIET \
  GRUB_TIMEOUT \
  GRUB_DEFAULT_BUTTON \
  GRUB_HIDDEN_TIMEOUT_BUTTON \
  GRUB_TIMEOUT_BUTTON \
  GRUB_BUTTON_CMOS_ADDRESS \
  GRUB_DISTRIBUTOR \
  GRUB_CMDLINE_LINUX \
  GRUB_CMDLINE_LINUX_DEFAULT \
  GRUB_CMDLINE_XEN \
  GRUB_CMDLINE_XEN_DEFAULT \
  GRUB_CMDLINE_NETBSD \
  GRUB_CMDLINE_NETBSD_DEFAULT \
  GRUB_TERMINAL_INPUT \
  GRUB_TERMINAL_OUTPUT \
  GRUB_SERIAL_COMMAND \
  GRUB_DISABLE_LINUX_UUID \
  GRUB_DISABLE_LINUX_RECOVERY \
  GRUB_DISABLE_NETBSD_RECOVERY \
  GRUB_VIDEO_BACKEND \
  GRUB_GFXMODE \
  GRUB_BACKGROUND \
  GRUB_THEME \
  GRUB_GFXPAYLOAD_LINUX \
  GRUB_DISABLE_OS_PROBER \
  GRUB_INIT_TUNE \
  GRUB_SAVEDEFAULT \
  GRUB_BADRAM

変数を環境変数にしている。
前半がスクリプトによって設定された変数。
後半が/etc/default/grubの中で定義されている(もしくは定義する)変数。


if test "x${grub_cfg}" != "x"; then
  rm -f ${grub_cfg}.new
  exec > ${grub_cfg}.new

  # Allow this to fail, since /boot/grub/ might need to be fatfs to support some
  # firmware implementations (e.g. OFW or EFI).
  chmod 400 ${grub_cfg}.new || grub_warn "Could not make ${grub_cfg}.new readable by only root.\
  This means that if the generated config contains a password it is readable by everyone"
fi

grub_cfgが空でなければ、つまり出力ファイルが指定されていればthen以降が実行される。
grub_cfgが/boot/grub/grub.cfgの場合、標準出力を/boot/grub/grub.cfg.newにリダイレクトする。


echo "Generating grub.cfg ..." >&2

標準エラー出力にgrub.cfgを生成しているメッセージを出力する。


cat << EOF
#
# DO NOT EDIT THIS FILE
#
# It is automatically generated by $self using templates
# from ${grub_mkconfig_dir} and settings from ${sysconfdir}/default/grub
#
EOF

コメントを出力。


for i in ${grub_mkconfig_dir}/* ; do
  case "$i" in
    # emacsen backup files. FIXME: support other editors
    *~) ;;
    *)
      if grub_file_is_not_garbage "$i" && test -x "$i" ; then
        echo
        echo "### BEGIN $i ###"
        "$i"
        echo "### END $i ###"
      fi
    ;;
  esac
done

/etc/grub.d/にあるファイルにすべてについてcase文以降が実行される。
ファイルがエディタ等で生成されたバックアップファイルなら何もしない。
ファイルを引数にgrub_file_is_not_garbageが実行される。
grub_file_is_not_garbageは/usr/lib/grub/grub-mkconfig_libで定義されている。
パッケージ関連のファイルとREADMEファイルは何もしない。
実行ファイルならばthen以降が実行される。
### BEGIN ファイル ###を出力する。
ファイルを実行する。
### END ファイル ###を出力する。


if [ "x${grub_cfg}" != "x" ] && ! grep -q "^password " ${grub_cfg}.new ; then
  chmod 444 ${grub_cfg}.new || true
fi

grub_cfgが空でなければ、つまり出力ファイルが指定されていれば標準出力をリダイレクトしているファイルにpasswordの文字列があるか調べる。
含まれていなければ誰でも読めるようにする。


if test "x${grub_cfg}" != "x" ; then
  # none of the children aborted with error, install the new grub.cfg
  mv -f ${grub_cfg}.new ${grub_cfg}
fi

grub_cfgが空でなければ、つまり出力ファイルが指定されていればthen以降が実行される。
grub_cfgが/boot/grub/grub.cfgの場合/boot/grub/grub.cfg.newを/boot/grub/grub.cfgにリネーム。


echo "done" >&2

標準エラー出力で完了したことを伝える。


[Top]