Ubuntu20.04 電源ブチ切り myinit Ver.2

このmyinitは、initrdで地ならしした環境でないと、うまくいかないようです。x86系では問題なく適用できましたが、ARMではダメでした。少なくとも、多重ループを避けた記述をしたり、記述によってはprocのマウントを強いられるなど、難しい対応となるため、ARMではmyinit Ver.1でいいかと思います。initrdの無い環境で/etc/myrwtabを活用する良い方法が無いか模索中です。


Oracle Linux 9/RHEL9での電源ブチ切りでは、メモリに飛ばすfileとdirを定義ファイル(/etc/rwtab)で指定できる汎用性がありました。この仕組みをUbuntu20.04にも取り入れたいと思います。本当はx86_64ではinitramfsのフェーズでメモリに飛ばすべきですが、grubやinitramfsを適用していない組込のケースも視野に入れ、/sbin/myinitで/etc/(my)rwtabを参照しながらメモリに展開することにします。
なお、繰り返しになりますが、この実装でよく起こる初期トラブルは、/homeや/varの下で膨れ上がったユーザデータとキャッシュの処置漏れによるメモリ容量オーバです。ご利用は自己責任でお願いします。OSが起動しなくなったら、リカバリモードで起動して調整すれば復旧できます。

bindマウントとは

既存ファイルシステムツリーの枝葉に、別のツリーを上から被せるイメージです。dirだけでなくfileのbindも可能です。RHEL系Stateless実装では、以下の流れになります。
 ① RWする予定のfileとdirをtmpfs領域(RAMDISK)にコピー。
 ② 元のファイルシステムツリーに①を上から被せる。
bindというよりはoverlayなのですが、overlayfsは別の実装です。Stateless(tmpfsroot)方式は、overlayfs方式での長期運用に懸念を感じる故の実装です。

流用元実装の解析

/etc/rwtabの文法:マニュアル
実行スクリプト: 手元のOracle Linux9でdnf install readonly-root後の/usr/libexec/readonly-rootを見るか、githubを確認します。以下は要所のピックアップであり、##コメントでフォローしました。

MOUNTS=()  ## bindマウントのリストを宣言
if is_true "$READONLY" || is_true "$TEMPORARY_STATE"; then

    add_mount() {
        mnt=${1%/} ## 末尾の「/」を削除
        MOUNTS=("${MOUNTS[@]}" "$mnt")  ## リストにマウントポイントを追加
    }

    cp_empty() {   ## dir先頭のみが空の状態でtmpfs($RW_MOUNT)にコピーされる
        if [ -e "$1" ]; then
            echo "$1" | cpio -p -vd "$RW_MOUNT" &>/dev/null
            add_mount $1  ## 後でbindマウントするためリストに追加
        fi
    }

    cp_dirs() {   ## dirツリー構造全体が空の状態でtmpfs($RW_MOUNT)にコピーされる
        if [ -e "$1" ]; then
            mkdir -p "$RW_MOUNT$1"
            find "$1" -type d -print0 | cpio -p -0vd "$RW_MOUNT" &>/dev/null
            add_mount $1  ## 後でbindマウントするためリストに追加
        fi
    }

    cp_files() {   ## fileやdirがそのままcp -a(全コピー)でtmpfs($RW_MOUNT)にコピーされる
        if [ -e "$1" ]; then
            cp -a --parents "$1" "$RW_MOUNT"
            add_mount $1  ## 後でbindマウントするためリストに追加
        fi
    }

### 中略

    for file in /etc/rwtab /etc/rwtab.d/* /run/initramfs/rwtab ; do
        is_ignored_file "$file" && continue     ## .bakなどの拡張子ファイルは除外
        [ -f $file ] && while read type path ; do    ## /etc/rwtabから順次エントリを読み、処置
            case "$type" in
                empty)
                    cp_empty $path
                    ;;
                files)
                    cp_files $path
                    ;;
                dirs)
                    cp_dirs $path
                    ;;
                *)
                    ;;
            esac
        done < <(cat $file)
    done

    ## 親dirがあるエントリを除外しbindマウント
    for m in "${MOUNTS[@]}"; do
        prefix=0
        for mount_point in "${MOUNTS[@]}"; do
            [[ $m = $mount_point ]] && continue  ## 完全一致は判定スキップ 
            if [[ $m =~ ^$mount_point/.* ]] ; then   ## 親dir有りなら除外
                prefix=1
                break
            fi
        done
        [[ $prefix -eq 1 ]] && continue

        mount -n --bind $bindmountopts "$RW_MOUNT$m" "$m"
        selinux_fixup "$m"
    done

### 以下略


新しい/sbin/myinit (Ver.2)

上記を/sbin/myinitに、ほぼそのままの形で流用します。
/etc/myrwtabを読む所ではforループ不要ですが、汎用性や拡張性を優先するLinuxの文化に習い、あえてそのままとしました。

#!/bin/bash
# myinit Ver 2.0

if [ -e /.tmpfsroot ] && [ -d /tmpfsroot ] \
        && [ -e /etc/fstab_ro ] && [ -e /etc/myrwtab ]; then
    /usr/games/cowsay "TMPFSROOT Start!"

    ## tmpfsを作成
    RW_MOUNT="/tmpfsroot"
    mount -t tmpfs tmpfs $RW_MOUNT

    ## Start
    MOUNTS=()  ## bindマウントのリストを宣言
    add_mount() {
        mnt=${1%/} ## 末尾の「/」を削除
        MOUNTS=("${MOUNTS[@]}" "$mnt")  ## リストにマウントポイントを追加
    }
    cp_empty() {   ## dir先頭のみが空の状態でtmpfs($RW_MOUNT)にコピーされる
        if [ -e "$1" ]; then
            echo "[myinit] cp_empty $1 $RW_MOUNT" 
            echo "$1" | cpio -p -vd "$RW_MOUNT" &>/dev/null
            add_mount $1
        fi
    }
    cp_dirs() {   ## dirツリー構造全体が空の状態でtmpfs($RW_MOUNT)にコピーされる
        if [ -e "$1" ]; then
        echo "[myinit] cp_dirs (include symlink) $1 $RW_MOUNT" 
            mkdir -p "$RW_MOUNT$1"
            find "$1" -type d -print0 | cpio -p -0vd "$RW_MOUNT" &>/dev/null
        ## 改良:シンボリックリンクもコピーする
            find "$1" -type l -print0 | cpio -p -0vd "$RW_MOUNT" &>/dev/null
            add_mount $1
        fi
    }
    cp_files() {   ## fileやdirがそのままcp -a(全コピー)でtmpfs($RW_MOUNT)にコピーされる
        if [ -e "$1" ]; then
            echo "[myinit] cp_files -a --parents $1 $RW_MOUNT" 
            cp -a --parents "$1" "$RW_MOUNT"
            add_mount $1
        fi
    }

    ## /etc/myrwtabから順次エントリを読み、処置
#    for file in /etc/myrwtab ; do
#        [ -f $file ] && while read type path; do
        while read type path; do
            case "$type" in
                empty)   cp_empty $path ;;
                files)   cp_files $path ;;
                dirs)    cp_dirs  $path ;;
                *)       ;;
            esac
#        done < <(cat $file)
#    done
        done < "/etc/myrwtab"

    ## 親dirがあるエントリを除外しbindマウント
    for m in "${MOUNTS[@]}"; do
        prefix=0
        for mount_point in "${MOUNTS[@]}"; do
            [[ $m = $mount_point ]] && continue  ## 完全一致は判定スキップ 
            if [[ $m =~ ^$mount_point/.* ]] ; then   ## 親dir有りなら除外
                prefix=1
                break
            fi
        done
        [[ $prefix -eq 1 ]] && continue

        mount -n --bind "$RW_MOUNT$m" "$m"
        echo $m >> ${RW_MOUNT}/mount_list  ## for debug
    done

    ## tmpfsroot ユーザランド調整
    [ -e /etc/fstab_ro ]        && mv -f /etc/fstab_ro /etc/fstab
    [ -e /etc/issue_tmpfsroot ] && mv -f /etc/issue_tmpfsroot /etc/issue
    [ -e /etc/motd_tmpfsroot  ] && mv -f /etc/motd_tmpfsroot  /etc/motd

    ## Add the sticky bit 
    chmod 1777 /tmp 
    chmod 1777 /var/tmp
else
    echo "[myinit] normal boot ..."
fi
## Start systemd
exec /sbin/init


/etc/myrwtabの例

/var/lib の扱いが難しいのですが、まずは下記コマンドでリストを作ります。
find /var/lib -maxdepth 1 -mindepth 1 | sort | xargs -I{} echo "files {}" >> /etc/myrwtab
次に、容量の大きいdirを下記コマンドで確認します。
du --max-depth 1 -h /var/lib | sort -hr
容量の大きいdirや、ReadOnlyで良いことがわかっているdirは、コメントアウトします。

## /tmp
empty   /tmp
empty   /var/tmp

## /var
dirs    /var/backups
dirs    /var/cache
dirs    /var/crash
#files  /var/lib
dirs    /var/local
files   /var/lock
dirs    /var/log
files   /var/log/xrdp.log
files   /var/log/xrdp-sesman.log
dirs    /var/mail
files   /var/metrics
dirs    /var/opt
files   /var/run
# files /var/snap
fiies   /var/spool

## for Login
files   /etc
files   /root
files   /home

## /var/lib
files   /var/lib/AccountsService
files   /var/lib/BrlAPI
files   /var/lib/NetworkManager
files   /var/lib/PackageKit
files   /var/lib/VBoxGuestAdditions
files   /var/lib/acpi-support
files   /var/lib/alsa
files   /var/lib/app-info
files   /var/lib/apport
#files   /var/lib/apt
files   /var/lib/aspell
files   /var/lib/avahi-autoipd
files   /var/lib/bluetooth
files   /var/lib/boltd
files   /var/lib/colord
files   /var/lib/command-not-found
files   /var/lib/dbus
files   /var/lib/dhcp
files   /var/lib/dictionaries-common
#files   /var/lib/dpkg
files   /var/lib/emacsen-common
files   /var/lib/fprint
files   /var/lib/fwupd
files   /var/lib/gdm3
files   /var/lib/geoclue
files   /var/lib/ghostscript
files   /var/lib/grub
files   /var/lib/hp
files   /var/lib/initramfs-tools
files   /var/lib/ispell
files   /var/lib/libreoffice
files   /var/lib/locales
files   /var/lib/logrotate
files   /var/lib/man-db
files   /var/lib/misc
files   /var/lib/openvpn
files   /var/lib/os-prober
files   /var/lib/pam
files   /var/lib/plymouth
files   /var/lib/polkit-1
files   /var/lib/private
files   /var/lib/python
files   /var/lib/sgml-base
#files   /var/lib/snapd
files   /var/lib/snmp
files   /var/lib/sudo
files   /var/lib/systemd
files   /var/lib/tpm
files   /var/lib/ubiquity
files   /var/lib/ubuntu-advantage
files   /var/lib/ubuntu-drivers-common
files   /var/lib/ubuntu-release-upgrader
files   /var/lib/ucf
files   /var/lib/udisks2
files   /var/lib/unattended-upgrades
files   /var/lib/update-manager
files   /var/lib/update-notifier
files   /var/lib/upower
files   /var/lib/usb_modeswitch
files   /var/lib/usbutils
files   /var/lib/vim
files   /var/lib/whoopsie
files   /var/lib/xfonts
files   /var/lib/xkb
files   /var/lib/xml-core