組み込みシステム構築の定石

目次

8.1. 起動時にコマンドを自動実行する
8.1.1. systemdとは
8.1.2. 自動起動するコマンドの作成
8.1.3. Unitファイルの作成
8.1.4. 自動起動の設定
8.1.5. 自動起動の解除
8.1.6. 順序関係のあるコマンドの自動起動
8.2. 定期的にコマンドを実行する
8.2.1. systemdで定期実行する
8.3. 不具合発生時の自動再起動
8.3.1. systemdによるプロセスの自動再起動
8.3.2. ウォッチドッグタイマーによるシステムの再起動
8.4. ログ管理
8.4.1. ログファイルのローテーション
8.4.2. ログをリモートサーバーに送る
8.5. 外部ストレージのデータを守る
8.5.1. データがストレージに書き込まれたことを保証する
8.5.2. 不意な電源断への対応
8.6. ファイアーウォールを設定する
8.6.1. すべてのパケットを破棄する
8.6.2. SSHを許可する
8.6.3. HTTPSを許可する
8.6.4. 設定値を保存する
8.7. SSHのセキュリティ設定を見直す
8.7.1. パスワードの代わりにRSA公開鍵認証を使う
8.7.2. 使用するポートを変更する
8.8. ソフトウェアアップデート
8.8.1. Gitによるソースコードの管理
8.8.1.1. Gitリポジトリの作成
8.8.1.2. ベースとなるソースコードの登録
8.8.1.3. ソースコードの修正
8.8.1.4. ベースとなるソースコードの更新
8.8.1.5. 更新後のベースとなるソースコードにソースコードの修正を適用
8.8.2. Armadilloのソフトウェア更新における2つの考え方
8.8.3. インストールディスクを使用してのアップデート
8.8.4. インターネットを使用してのアップデート
8.8.4.1. APT でインストールしたソフトウェアのアップデート
8.8.4.2. APT でインストールしたソフトウェア以外のソフトウェアのアップデート
8.9. PCとArmadillo間でファイルを共有する
8.9.1. sambaをインストールする
8.9.2. sambaを設定する
8.9.3. Windowsからアクセスする
8.9.4. ATDEからアクセスする

本章では、組み込みシステムを構築する際の定石を紹介します。

8.1. 起動時にコマンドを自動実行する

システム起動時にコマンドを自動的に実行するにはsystemdを利用します。

本章では、この方法について説明します。

8.1.1. systemdとは

systemdとはLinuxのシステム管理デーモンの一つです。 並列実行による効率的なシステム起動/終了や設定ファイルによるシステム管理の共通化、柔軟なプロセス起動を行うことができます。 Armadillo-640ではデフォルトのシステム管理デーモンにsystemdを採用しています。

[注記]

詳細な情報は公式サイトを参照してください。

https://www.freedesktop.org/wiki/Software/systemd/

8.1.2. 自動起動するコマンドの作成

起動時に自動実行するテストコマンドを作成します。一秒ごとに/tmp/system_init_test.logに"system_init_test"という文字列を書き込むだけの簡単なシェルスクリプトです。

[armadillo ~]# vi system_init_test.sh
[armadillo ~]# chmod +x system_init_test.sh 1

1

実行権限を付けます。

シェルスクリプトの内容は以下のように実装します。

#!/bin/bash

while true
do
   echo system_init_test >> /tmp/system_init_test.log
   sleep 1
done

図8.1 シェルスクリプトの実装内容(system_init_test.sh)


8.1.3. Unitファイルの作成

systemdではUnitと呼ばれる定義ファイルに起動時の定義を記載することで各起動処理を行っています。 自作のコマンドの場合も、Unitファイルを作成することでシステム起動時に自動起動することができます。

ここでは「system_init_test.service」という名前のUnitファイルの設定を行います。

[armadillo ~]# vi /etc/systemd/system/system_init_test.service

Unitファイルの内容は以下のように実装します。

[Unit]
Description = system_init_test daemon

[Service]
ExecStart = /root/system_init_test.sh
Restart = always
Type = simple

[Install]
WantedBy = multi-user.target

図8.2 Unitファイルの実装内容(system_init_test.service)


Unitファイルの各項目と意味については以下の通りです。

  • [Unit]セクション:起動時に必要な依存関係など

    • Description:このUnitの説明文を記載する。
    • Before:このUnitよりも後に起動するUnitを指定する。(指定したUnitより前に、このUnitを起動する。)
    • After:このUnitよりも前に起動するUnitを指定する。(指定したUnitより後に、このUnitを起動する。)
    • Wants:このUnitの起動に必要なUnitを指定する。可能な限り同時起動する。
    • Requires:このUnitの起動に必要なUnitを指定する。必ず同時起動する。
  • [Service]セクション:起動時に必要なパラメータなど

    • Type:このUnitが起動完了したかどうかの判定方法を指定する。

      • simple:コマンドが実行された時に起動完了となる。
      • forking:コマンドが完了した時に起動完了となる。
      • oneshot:コマンドが完了した時に起動完了&終了となる。
      • notify:このUnitのプログラム内からsystemdへシグナルを送った時に起動完了となる。
    • ExecStart:このUnitを起動する際に実行するコマンドを指定する。(絶対パスを指定)
    • ExecReload:このUnitをリロードする際に実行するコマンドを指定する。
    • ExecStop:このUnitを停止する際に実行するコマンドを指定する。
    • Restart:このUnitのメインプロセスが停止した際の動作を指定する。

      • always:常に再起動を試みる。
      • no:再起動を行わない。(デフォルト値)
      • on-success:終了コード0で停止した際に再起動する。
      • on-failure:終了コード0以外で停止した際に再起動する。
  • [Install]セクション:systemdで有効にした場合の設定など

    • WantedBy:このUnitが必要とするtargetを指定する。可能な限り同時起動する。
    • RequiredBy:このUnitが必要とするtargetを指定する。必ず同時起動する。
[注記]

一般的なUnitの場合、WantedBy/RequiredByに指定する値はmulti-user.targetまたは、graphical.targetを指定します。

WantedBy/RequiredByにtargetを指定すると/etc/systemd/system/[target].wants/配下に対象となるUnitへのシンボリックリンクが作成されます。

これは[Unit]セクションのWants/Requiresに依存関係が追加されるのと同じ効果を持ち、以下のように使い分けをします。

  • システム的に必要な依存関係 → [Unit]セクションのWants/Requiresに記載
  • システム管理者が環境に応じて設定する依存関係 → WantedBy/RequiredByに記載

8.1.4. 自動起動の設定

systemctlコマンドを用いて自動起動するserviceの設定をします。

[armadillo ~]# systemctl enable system_init_test.service
Created symlink /etc/systemd/system/multi-user.target.wants/system_init_test.service -> /etc/systemd/system/system_init_test.service.

[armadillo ~]# systemctl list-unit-files | grep system_init_test
system_init_test.service               enabled 1

1

有効になっていることを確認できます。

systemctlのstartサブコマンドでserviceを起動できます。

[armadillo ~]# systemctl start system_init_test.service
[armadillo ~]# LANG=C systemctl status system_init_test.service 1
* system_init_test.service - system_init_test daemon
   Loaded: loaded (/etc/systemd/system/system_init_test.service; enabled; vendor
   Active: active (running) since Tue 2019-04-16 09:49:53 JST; 46s ago
 Main PID: 747 (system_init_tes)
    Tasks: 2 (limit: 4915)
   CGroup: /system.slice/system_init_test.service
           |-747 /bin/bash /root/system_init_test.sh
           `-806 sleep 1

1

serviceのステータスが確認できます。

[ティップ]

LANG=Cを設定しないと表示結果が文字化けすることがあります。

実行結果は以下の通り確認できます。

[armadillo ~]# tail -f /tmp/system_init_test.log
system_init_test
system_init_test
system_init_test

systemctlのstopサブコマンドでserviceを停止できます。

[armadillo ~]# systemctl stop system_init_test.service

Armadilloを再起動することで設定したserviceが自動起動されているかを確認できます。

[armadillo ~]# reboot
(省略)
[armadillo ~]# tail -f /tmp/system_init_test.log
system_init_test
system_init_test
system_init_test

8.1.5. 自動起動の解除

systemctlのdisableサブコマンドで自動起動を解除できます。

[armadillo ~]# systemctl disable system_init_test.service
[armadillo ~]# systemctl list-unit-files | grep system_init_test
system_init_test.service               disabled 1

1

解除されていることを確認できます。

8.1.6. 順序関係のあるコマンドの自動起動

あるコマンドを起動した後に別のコマンドを実行したい、といったような順序関係のあるコマンドの自動起動について説明します。

2つのテストコマンドを作成します。/tmp/system_init_test_ab.logに"system_init_test_a","system_init_test_b"という文字列を書き込むだけの簡単なシェルスクリプトです。

[armadillo ~]# vi system_init_test_a.sh
[armadillo ~]# vi system_init_test_b.sh
[armadillo ~]# chmod +x system_init_test_a.sh
[armadillo ~]# chmod +x system_init_test_b.sh

シェルスクリプトの内容は以下のように実装します。

#!/bin/bash

echo system_init_test_a >> /tmp/system_init_test_ab.log

図8.3 シェルスクリプトの実装内容(system_init_test_a.sh)


#!/bin/bash

echo system_init_test_b >> /tmp/system_init_test_ab.log

図8.4 シェルスクリプトの実装内容(system_init_test_b.sh)


Unitファイルを作成します。ここではsystem_init_test_a.shを起動した後にsystem_init_test_b.shを起動するように設定します。

[armadillo ~]# vi /etc/systemd/system/system_init_test_a.service
[armadillo ~]# vi /etc/systemd/system/system_init_test_b.service

Unitファイルの内容は以下のように実装します。

[Unit]
Description = system_init_test_a

[Service]
ExecStart = /root/system_init_test_a.sh
Type = oneshot

[Install]
WantedBy = multi-user.target

図8.5 Unitファイルの実装内容(system_init_test_a.service)


[Unit]
Description = system_init_test_b
After = system_init_test_a.service 1

[Service]
ExecStart = /root/system_init_test_b.sh
Type = oneshot

[Install]
WantedBy = multi-user.target

図8.6 Unitファイルの実装内容(system_init_test_b.service)


1

先に起動するUnitを指定します。 ここで指定するUnit(この場合は 図8.5「Unitファイルの実装内容(system_init_test_a.service)」 )が "Type = oneshot" であることに注意してください。 "Type = oneshot" であれば、 system_init_test_b.service は、確実に system_init_test_a.service が終了した後に起動されます。

自動起動の設定を行います。

[armadillo ~]# systemctl enable system_init_test_a.service
[armadillo ~]# systemctl enable system_init_test_b.service

Armadilloを再起動して確認します。

[armadillo ~]# reboot
(省略)
[armadillo ~]# cat /tmp/system_init_test_ab.log
system_init_test_a
system_init_test_b 1

1

設定した順に実行されていることが確認できます。

8.2. 定期的にコマンドを実行する

前章では、 起動時にコマンドを実行する方法を紹介しました。 本章では、 指定した時刻に定期的にコマンドを実行する方法として、 systemdを利用する方法を紹介します。

8.2.1. systemdで定期実行する

systemdで定期実行するには、 定期実行したいプログラムを起動するUnitファイルとは別に、 もう一つUnitファイルを作成します。

ここでは、 「順序関係のあるコマンドの自動起動」 で作成した 図8.5「Unitファイルの実装内容(system_init_test_a.service)」 を1分毎に起動するようにしてみます。

system_init_test_a.sh、 system_init_test_a.service の内容を確認します。 もし削除してしまった場合は、 もう一度作成してください。

[armadillo ~]# vi system_init_test_a.sh
[armadillo ~]# chmod +x system_init_test_a.sh
[armadillo ~]# vi /etc/systemd/system/system_init_test_a.service

シェルスクリプトの内容は以下のように実装します。

#!/bin/bash

echo system_init_test_a >> /tmp/system_init_test_ab.log

図8.7 シェルスクリプトの実装内容(system_init_test_a.sh)


Unitファイルの内容は以下のように実装します。

[Unit]
Description = system_init_test_a

[Service]
ExecStart = /root/system_init_test_a.sh
Type = oneshot

[Install]
WantedBy = multi-user.target

図8.8 Unitファイルの実装内容(system_init_test_a.service)


タイマーを設定するために、 system_init_test_a.timer というUnitファイルを system_init_test_a.service と同じディレクトリに作成します。

[armadillo ~]# vi /etc/systemd/system/system_init_test_a.timer

Unitファイルの内容は以下のように実装します。

[Unit]
Description = launch system_init_test_a

[Timer]
Persistent = true
OnBootSec = 0s
OnCalendar = *-*-* *:*:00
AccuracySec = 0.1s

[Install]
WantedBy = timers.target

図8.9 Unitファイルの実装内容(system_init_test_a.timer)


Unitファイル(.timer)の [Timer] の意味については以下の通りです。 [Unit][Install] については、 Unitファイル(.service)と同じなので省きます。

  • [Timer]セクション: 対象のUnitを起動するタイミングを指定する。

    • OnActiveSec: タイマーがactiveになってから指定時間が経過した後、対象のUnitを起動する。
    • OnBootSec: システムがブートしてから指定時間が経過した後、対象のUnitを起動する。
    • OnStartupSec: systemdがスタートしてから指定時間が経過した後、対象のUnitを起動する。
    • OnUnitActiveSec: 対象のUnitが最後に起動になってから指定時間が経過した後、対象のUnitを起動する。
    • OnUnitInactiveSec: 対象のUnitが最後に停止になってから指定時間が経過した後、対象のUnitを起動する。
    • OnCalendar: 指定時間になった時、対象のUnitを起動する。

      • 曜日 年-月-日 時:分:秒: 曜日(Mon, Tue, Wed, Thu, Fri, Sat, Sun)は省略できる。 各フィールドにはアスタリスク(*)も指定できる。 アスタリスクを指定した場合、 そのフィールドが取りうるすべての値を指定したことになる。
      • minutely: --* ::00 と同じ。
      • hourly: --* *:00:00 と同じ。
      • daily: --* 00:00:00 と同じ。
      • quarterly: *-01,04,07,10-01 00:00:00 と同じ。
    • Persistent=: 電源停止などで前回未実行だった場合の動作を指定する。

      • true: 対象のUnitを即座に起動する。
      • false: 即座に起動はせず、次のタイマーの時に対象のUnitを起動する。
    • AccuracySec=: 対象のUnitを起動するタイミングの精度を指定する。デフォルトでは 1min 。 (秒単位の精度がほしい場合は 0.1s などとする。)
[ティップ]

上記の指定可能な値以外にも高度な設定が可能です。

詳細は、 man 5 systemd.timer や man 7 systemd.time を参照してください。

タイマーの自動起動の設定を行います。 また、system_init_test_a.service が system_init_test_a.timer 以外から起動されると混乱の元になってしまうので、 system_init_test_a.service の自動起動しないように設定します。

[armadillo ~]# systemctl enable system_init_test_a.timer
[armadillo ~]# systemctl disable system_init_test_a.service

Armadilloを再起動して確認します。

[armadillo ~]# reboot
(省略)
[armadillo ~]# tail -f /tmp/system_init_test_ab.log
system_init_test_b 1
system_init_test_a 2
system_init_test_a 3

1

system_init_test_b.service には、 After = system_init_test_a.service が設定されていますが、 system_init_test_a.service の自動起動が無効化されているので、 system_init_test_a.service とは無関係に system_init_test_b.service が起動しています。

2

system_init_test_a.timer の OnBootSec=0s によって system_init_test_a.service が起動されています。

3

しばらく待つと system_init_test_a.timer の OnCalendar=--* ::00 によって system_init_test_a.service が起動されます。

8.3. 不具合発生時の自動再起動

一度起動したシステムがいつまでも元気に動き続けるという保証はありません。 システムが正常に動かなくなる原因には、ソフトウェアのバグやハードウェア の故障などが考えられます。

システムは停止することがある、ということを前提として、停止した場合の対 処についても、システム設計時に考慮しておかなければなりません。ここでは、 問題が発生した場合、自動的に再起動する方法について説明します。

8.3.1. systemdによるプロセスの自動再起動

「Unitファイルの作成」で紹介したように、 systemdによって起動されたプロセスがなんらかの理由により意図せず終了してしまった場合、 Unitファイルの定義に従ってプロセスを再起動します。

プロセスで起動時や一定時間毎にログを出力しておけば、 いつ頃異常終了したかを特定できるので、 障害解析時に重要な情報となります。

8.3.2. ウォッチドッグタイマーによるシステムの再起動

アプリケーションプログラムのプロセスが異常終了したのであれば、 systemdによってそのことを検出し、再起動することができます。 しかし、カーネルがハングアップ(停止)してしまった場合、 ソフトウェア的にそのことを検出する手段はありません。

システム全体がハングアップしてしまった場合、 そのことを検出するための仕組みとして、ウォッチドッグタイマーがあります。

ウォッチドッグタイマーは、有効にされると内部のタイマーのカウントを開始します。 システム側は、正常に動作している間はウォッチドッグタイマーに対して、 定期的に信号を送ります[45]。 ウォッチドッグタイマーは、信号を受け取るとタイマーのカウントを最初からやり直します。 もし、システム側に問題が発生し、信号を送ることができなくなったら、 ウォッチドッグタイマーがタイムアウトし、システムのリセットをおこないます。

Armadilloでは、標準でハードウェアウォッチドッグタイマーが有効になっています。

ブートローダーによって、 ウォッチドッグタイマーのタイムアウト時間が設定されてから、 Linuxカーネルが起動されます。 カーネルは、自動でウォッチドッグタイマーをキックします。

もし、何らかの要因でカーネルがハングアップしてウォッチドッグタイマーをキックできなくなりタイムアウトが発生すると、 システムが再起動します。

Linuxカーネルに手を入れるようなことがなければ、通常、 カーネルのハングアップに気を配る必要はありませんが、 このような仕組みがArmadilloには組み込まれていることを覚えておいてください。

8.4. ログ管理

Linuxシステムでは、ログの管理はsyslogでおこなうことが一般的です。

各アプリケーションプログラムは、loggerコマンドやC言語のsyslog関数で、 ログ記録用プログラムであるsyslogデーモンにメッセージを送ります。 syslogデーモンは、送られてきたメッセージをファイルに記録したり、 別サーバーへの転送、ログファイルのローテーションなど、 ログの管理を一括して行います。

syslogデーモンには、オリジナルのsyslogdの他に、 いくつかのバリエーションがあります。

Armadillo-600シリーズで採用している Debian GNU/Linux 9.0(コードネーム "stretch")では、 rsyslogdが標準です。[46]

8.4.1. ログファイルのローテーション

ログファイルにログを書き込み続けると、ファイルサイズがどんどん大きくなり、 いずれストレージの限界に達してしまいます。 このような事態を避けるため、通常、 ログファイルのローテーションをおこないます。 ログファイルのローテーションは、一定の期間(1日や1週間など)や、 一定のサイズごとに行います。

ローテーションの設定は、 /etc/logrotate.conf と /etc/logrotate.d/ 以下のファイルで行います。 /etc/logrotate.d ディレクトリの中を見てみると apt や rsyslog などの Armadillo にインストールされているソフトウェア毎に、 設定ファイルが作られていることがわかります。

/etc/logrotate.conf の中には、説明とともに設定が記述されています。

[armadillo ~]# cat /etc/logrotate.conf
# see "man logrotate" for details
# rotate log files weekly
weekly  1

# keep 4 weeks worth of backlogs
rotate 4 2

# create new (empty) log files after rotating old ones
create 3

# uncomment this if you want your log files compressed
#compress 4

# packages drop log rotation information into this directory
include /etc/logrotate.d 5

# no packages own wtmp, or btmp -- we'll rotate them here
/var/log/wtmp { 6
    missingok
    monthly
    create 0664 root utmp
    rotate 1
}

/var/log/btmp {
    missingok
    monthly
    create 0660 root utmp
    rotate 1
}

# system-specific logs may be configured here  7

1

ログファイルをローテーションする頻度を weekly (毎週) に設定しています。

2

ローテーションしたログファイルを 4つ まで保存するように設定しています。

3

ログファイルをローテーションした直後に、空のログファイルを作成するように設定しています。

4

コメントアウトされているのでこの設定は無効です。 コメントアウトを外すとローテーションしたログファイルを圧縮するように設定します。

5

/etc/logrotate.d ディレクトリから設定ファイルを読み込んでいます。

6

/var/log/wtmp というログファイルに対して、 {} で囲まれた行に記述された設定を適用します。

7

最後の行に設定を記述することで、 その前までの行で記述された設定を上書きすることができます。

rsyslog のローテーションの設定を変更したい場合は、 /etc/logrotate.d/rsyslog を修正します。

[armadillo ~]# cat /etc/logrotate.conf
/var/log/syslog
{  1
        rotate 7
        daily
        missingok
        notifempty
        delaycompress
        compress
        postrotate
                invoke-rc.d rsyslog rotate > /dev/null
        endscript
}

/var/log/mail.info
/var/log/mail.warn
/var/log/mail.err
/var/log/mail.log
/var/log/daemon.log
/var/log/kern.log
/var/log/auth.log
/var/log/user.log
/var/log/lpr.log
/var/log/cron.log
/var/log/debug
/var/log/messages
{  2
        rotate 4
        weekly
        missingok
        notifempty
        compress
        delaycompress
        sharedscripts
        postrotate
                invoke-rc.d rsyslog rotate > /dev/null
        endscript
}

1

/var/log/syslog の設定をこの {} 内に記述しています。

2

直前に記述された /var/log/mail.info から /var/log/messages までの全てのファイルの設定をこの {} 内に記述しています。

表8.1 logrotate 設定ファイルの主な設定項目

項目 説明

missingok

対象のログファイルが存在しなくてもエラーを出さない。

notifempty

ログファイルが空ならローテーションしない。

rotate

ローテーションで保存するログファイル数。

size

指定したファイルサイズ以上になったらローテーションする。 追加指定された時間間隔より前にはローテーションしない。

maxsize

指定したファイルサイズ以上になったらローテーションする。 追加指定された時間間隔よりも前であってもローテーションする。

create

ローテーション後に空のログファイルを作成する。 パーミッション、ユーザ名、グループ名も設定できる。

daily

ログファイルを毎日ローテーションする。

weekly

ログファイルを毎週ローテーションする。

monthly

ログファイルを毎月ローテーションする。

yearly

ログファイルを毎年ローテーションする。

compress

ローテーションしたログファイルをgzipで圧縮する。

delaycompress

最新のローテーションは圧縮しない。

sharedscripts

対象のログファイルを複数指定している場合、 ローテーションするログファイルの数に関わらず、 postrotate または prerotate で指定したスクリプトを 1回だけ呼び出す。

nosharedscripts

ローテーションするログファイル一つに対して1回ずつ postrotate または prerotate で指定したスクリプトを呼び出す。 スクリプトの第1引数には、ローテーションするログファイルのフルパスが与えられる。

postrotate/endscript

ローテーションの後に postrotate から endscript までの間の行に記述されたスクリプトを呼び出す。

prerotate/endscript

ローテーションの前に prerotate から endscript までの間の行に記述されたスクリプトを呼び出す。


logrotate ではこの他にも様々な設定が可能です。 詳しくは、 man logrotate の CONFIGURATION FILE を参照してください。

8.4.2. ログをリモートサーバーに送る

rsyslogdは、 別のサーバーで動作しているsyslogデーモンへメッセージを転送できます。

メッセージを転送するには、 リモートサーバー(ここでは ATDE を使用します)とクライアント(ここでは Armadillo を使用します)の両方で適切な設定を行う必要があります。

大まかな手順は、次のようになります。

  1. /etc/rsyslog.conf の修正 (リモートサーバーのみ)
  2. /etc/rsyslog.d/aguide.conf の作成
  3. rsyslog の restart
  4. log の出力 (クライアントのみ)
  5. log の確認 (リモートサーバーのみ)

ファイアーウォールの設定をしている場合は、 上記の手順に、 rsyslogd が使用するポートを解放する手順が加わります。

メッセージの転送も、コマンドラインオプションで指定します。メッセージの 転送を指定するオプションは、-R HOST[:PORT]です。HOSTには、転送 先サーバーのIPアドレスかホスト名を指定します。PORTには、転送先サーバー のポート番号を指定します。デフォルトでは、514番ポートに転送します。

具体的な設定方法を、 ArmadilloからATDEにログを転送する場合を例として説明します。 ATDEのIPアドレスは192.168.0.1となっているとします。 メッセージの待ち受けには、TCPの514番ポートを使用します。

まず、ATDE側の設定をおこないます。 /etc/rsyslog.confのTCPの待ち受けに関する設定を有効にし、 /etc/rsyslog.d/aguide.confでログの出力先を設定します。 /etc/ 以下のファイルの編集には、特権ユーザー権限が必要なことに注意してください。 非特権ユーザーが特権ユーザー権限を行使するには、sudoコマンドを使います。

[ATDE ~]$ sudo vi /etc/rsyslog.conf

/etc/rsyslog.conf の次の部分を

: (省略)
# provides TCP syslog reception
# module(load="imtcp")
# input(type="imtcp" port="514")
: (省略)

以下のように修正します。

: (省略)
# provides TCP syslog reception
module(load="imtcp")
input(type="imtcp" port="514")
: (省略)

/etc/rsyslog.d/aguide.conf を作成し、 Armadillo から受け取ったログの出力先を記述します。

[ATDE ~]$ sudo vi /etc/rsyslog.d/aguide.conf

内容は以下のようにします。

armadillo.*        /var/log/armadillo.log

設定を反映させるために、rsyslogdを再起動します。

[ATDE ~]$ sudo service rsyslog restart

次に、Armadillo側の設定をおこないます。 /etc/rsyslog.d/aguide.confでログの出力先を設定します。

[armadillo ~]# vi /etc/rsyslog.d/aguide.conf

内容は以下のようにします。

armadillo.*        @@192.168.0.1

設定を反映させるために、rsyslogdを再起動します。

[armadillo ~]# service rsyslog restart

以上の設定を行うと、 ArmadilloでloggerコマンドまたはC言語のsyslog関数で送ったメッセージが、 ATDE5のrsyslogdによって /var/log/armadillo.log に記録されます。

8.5. 外部ストレージのデータを守る

Armadilloでは、USBメモリやSD/microSDカードなどの外部ストレージを使用することができます。 これらのストレージデバイスは、大容量のデータを保存するために大変便利ですが、 扱い方に注意が必要です。

本章では、外部ストレージのデータを安全に扱う方法を紹介します。

8.5.1. データがストレージに書き込まれたことを保証する

ストレージデバイスに対するデータの読み書き速度は、 CPUの動作速度やメモリの読み書きの速度と比べると、 非常に遅いです。 そのため、Linuxシステムでは、 色々な場所にバッファを設けてストレージとのデータのやりとりを効率化しています。 プログラムでファイルに対して書き込みを行った後、 そのデータがストレージデバイスに書き込まれたことを保証するためには、 すべてのバッファされているデータをフラッシュ(書き出し)しなければなりません。

ストリームに対するフラッシュは、fflushライブラリ関数でおこないます。 しかし、fflushライブラリ関数は、 ユーザー空間でバッファされているデータをフラッシュするだけで、 カーネル空間でバッファされているデータはフラッシュされませんので、 これでは不十分です。

syncシステムコールはカーネル空間のバッファをフラッシュします。 syncシステムコールは、デバイスへの書き込みが終了するまで返ってきません。 そのため、このシステムコールを使用すると、 データの書き込みを保証できるように思われます。 しかし、最近のストレージデバイスは、 デバイス側で大きなキャッシュを持っているため、 syncシステムコールから返ってきても、 データは実際にはストレージデバイスに書き込まれていないかもしれません。 syncコマンドでも同様です。

データがストレージに書き込まれたことを保証する最も確実な方法は、 umount システムコールか umount コマンドで、 デバイスをアンマウントすることです。 アンマウントが完了した時点で、 すべてのデータがストレージデバイスに書き込まれていることが保証されます。

8.5.2. 不意な電源断への対応

組み込みシステムでは、 予期せぬタイミングで電源が遮断される状況への対応は必須ともいえるでしょう。 特に、ストレージにデータを書き込みしている最中に電源断が発生すると、 どうしてもデータの不整合が発生して、ファイルシステムが破壊されてしまいます。

このような状況への対処として、ジャーナリングファイルシステムを用いる方法と、 ファイルシステムをリードオンリーでマウントする方法があります。

ジャーナリングファイルシステムとは、 定期的にファイルシステムの状態(ジャーナル)を保存しておくことで、 クラッシュが発生した場合に、ジャーナルを元に状態を復元できるファイルシステムです。 ジャーナリングファイルシステムを用いると、 フラッシュされていないデータがある状況で不意な電源断が発生した場合でも、 少し前の正常な状態にファイルシステムを復元することができます。

Linuxシステムで標準的なジャーナリングファイルシステムは、 ext4 ファイルシステムです。

ext4 ファイルシステムで、デバイスをフォーマットするには、 mkfs.ext4 コマンドを使用します。

ストレージへの書き込みが必要ない場合、 デバイスをリードオンリー(読み込みのみ)でマウントするのが最も確実で安全な方法です。 mountシステムコールやmountコマンドには、 リマウント(再マウント)オプションがあります。 書き込みが必要ない場合は、リードオンリーでマウントしておき、 書き込みが必要になった時だけ、読み書き可能でリマウントすることで、 ファイルシステムが破壊される危険性を少なくすることができます。 (もちろん、読み込みの必要すらないときは、 アンマウントしておくのが一番安全です。)

ストレージに保存するデータの内容をよく吟味して、 リードオンリーで済むデータがあるのならば、 デバイスをリードオンリーのパーティションと書き込み可能なパーティションに分割するという方法もあります。

パーティションを分割するには、fdiskコマンドを使用します。

8.6. ファイアーウォールを設定する

Linuxではiptablesコマンドを使用して、パケット フィルタリング型ファイアーウォールを構築することができます。パケットフ ィルタリングとは、通信されるデータ(パケット)に対して、IPアドレスやポ ート番号などの情報によって、送られてきたパケットを許可、破棄、または拒 否の判断を行う機能です。iptablesコマンドはこ のパケットフィルタリングのルールを設定することができます。

Armadillo-640でiptablesを使用するためにはLinuxカーネルコンフィギュレーションを変更して イメージファイルをビルドする必要があります。

Networking support --->
    Networking options --->
        Network packet filtering framework (Netfilter) --->
            Core Netfilter Configuration --->
                [*] Netfilter connection tracking support
Networking support --->
    Networking options --->
        Network packet filtering framework (Netfilter) --->
            Core Netfilter Configuration --->
                Netfilter Xtables support (required for ip_tables) --->
                    [*]  "conntrack" connection tracking match support
Networking support --->
    Networking options --->
        Network packet filtering framework (Netfilter) --->
            IP: Netfilter Configuration --->
                [*] IPv4 connection tracking support (required for NAT)
[注記]

iptablesの詳細な使用方法はman 8 iptablesを参照してください。

8.6.1. すべてのパケットを破棄する

ここではArmadilloに入ってくるすべてのパケットを破棄する設定を行います。 まず、現在のiptablesの設定を確認するために、以下のように実行します。

[armadillo ~]# iptables -L
Chain INPUT (policy ACCEPT)
target     prot opt source               destination

Chain FORWARD (policy ACCEPT)
target     prot opt source               destination

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination

現在の設定はINPUTがACCEPTになっているので、Armadilloに入ってくるすべてのパケットを通してしまいます。 すべてのパケットを破棄するにはINPUTとFORWARDをDROPに変更します。

[armadillo ~]# iptables -P INPUT DROP
[armadillo ~]# iptables -P FORWARD DROP
[armadillo ~]# iptables -L
Chain INPUT (policy DROP)
target     prot opt source               destination

Chain FORWARD (policy DROP)
target     prot opt source               destination

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination

これですべてのパケットを破棄するようになりましたが、このままではArmadilloから自分自身へのパケットも破棄されてしまうので、 ローカルループバックからのパケットは許可するように設定します。 さらに、Armadilloから外部へのリクエストに対するレスポンスのパケットも許可するように設定します。

[armadillo ~]# iptables -A INPUT -i lo -j ACCEPT
[armadillo ~]# iptables -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
[armadillo ~]# iptables -L
Chain INPUT (policy DROP)
target     prot opt source               destination
ACCEPT     all  --  anywhere             anywhere
ACCEPT     all  --  anywhere             anywhere             state RELATED,ESTA
BLISHED

(省略)
[armadillo ~]# ping localhost 1
PING localhost (127.0.0.1) 56(84) bytes of data.
64 bytes from localhost (127.0.0.1): icmp_seq=1 ttl=64 time=0.163 ms

1

自身からのパケットは許可されていることを確認できます。

8.6.2. SSHを許可する

SSHによる接続を許可したい場合は、以下のように設定します。

[armadillo ~]# iptables -A INPUT -p tcp -m tcp --dport 22 -j ACCEPT
[armadillo ~]# iptables -L
Chain INPUT (policy DROP)
target     prot opt source               destination
ACCEPT     all  --  anywhere             anywhere
ACCEPT     all  --  anywhere             anywhere             state RELATED,ESTA
ACCEPT     tcp  --  anywhere             anywhere             tcp dpt:ssh

(省略)

これで外部からArmadilloへのSSH接続ができるようになります。

8.6.3. HTTPSを許可する

SSHと同様の手順でHTTPSによる接続も許可することができます。

[armadillo ~]# iptables -A INPUT -p tcp -m tcp --dport 443 -j ACCEPT
[armadillo ~]# iptables -L
Chain INPUT (policy DROP)
target     prot opt source               destination
ACCEPT     all  --  anywhere             anywhere
ACCEPT     all  --  anywhere             anywhere             state RELATED,ESTA
ACCEPT     tcp  --  anywhere             anywhere             tcp dpt:ssh
ACCEPT     tcp  --  anywhere             anywhere             tcp dpt:https

(省略)

これで外部からArmadilloへのHTTPS接続ができるようになります。

8.6.4. 設定値を保存する

ここまでiptablesを使用してパケットフィルタリングを設定してきましたが、このままでは Armadilloを再起動すると設定値が元に戻ってしまうので、次のように設定値を保存する必要があります。

[armadillo ~]# apt-get update
[armadillo ~]# apt-get install iptables-persistent 1
[armadillo ~]# iptables-save > /etc/iptables/rules.v4

1

すでにインストール済みの場合は不要です。

これで次回起動時から自動的に設定値が適用されるようになります。

8.7. SSHのセキュリティ設定を見直す

ネットワークに接続されているArmadilloに対して外部からログインするセキュアな方法として SSHが挙げられます。しかしデフォルトの設定ではパスワードでの認証となっており十分にセキュアとは言えません。 この章では、パスワードの他にセキュリティを高める方法について説明します。

8.7.1. パスワードの代わりにRSA公開鍵認証を使う

ここではRSA公開鍵認証を使用したSSHログイン方法について説明します。

まず、クライアント側の環境で鍵ペアを作成します。ユーザ名はatmarkとします。

[PC ~]$ mkdir .ssh 1
[PC ~/.ssh]$ cd .ssh
[PC ~/.ssh]$ ssh-keygen -t rsa
Generating public/private rsa key pair.
Enter file in which to save the key (/home/atmark/.ssh/id_rsa): 2
Enter passphrase (empty for no passphrase): 3
Enter same passphrase again:

[PC ~/.ssh]$ ls
id_rsa  id_rsa.pub

1

すでにある場合は不要です。

2

鍵の保存先です。ここではデフォルト値とします。

3

秘密鍵を使用するためのパスフレーズです。設定したほうがよりセキュアです。

作成した公開鍵id_rsa.pubをArmadilloへ転送します。ここではscpを使った例を示します。

[PC ~/.ssh]$ scp id_rsa.pub atmark@[ArmadilloのIPアドレス]:~/

クライアント側での作業はここまでとなります。

[警告]秘密鍵の扱い

秘密鍵がコピーされてしまうと、なりすましが可能になってしまうので、 絶対に漏らさないように気をつけてください。

ここからはArmadillo側での作業となります。

[armadillo ~]# su atmark 1
[armadillo /root]$ cd 2
[armadillo ~]$ mkdir .ssh 3
[armadillo ~]$ cat ~/id_rsa.pub >> ~/.ssh/authorized_keys 4
[armadillo ~]$ chmod 700 ~/.ssh 5
[armadillo ~]$ chmod 600 ~/.ssh/authorized_keys 6
[armadillo ~]$ exit 7

1

atmarkユーザになります。

2

atmarkのホームディレクトリへ移動します。

3

すでにある場合は不要です。

4

公開鍵の内容をauthorized_keysに追記します。

5 6

パーミッションを適切に設定します。

7

rootに戻ります。

次にSSHサーバの設定を変更します。

[armadillo ~]# vi /etc/ssh/sshd_config

RSA公開鍵認証でログイン可能となるように設定します。加えてパスワードでのログインを不可とします。

:(省略)
RSAAuthentication yes 1
PubkeyAuthentication yes 2
# Expect .ssh/authorized_keys2 to be disregarded by default in future.
AuthorizedKeysFile      .ssh/authorized_keys .ssh/authorized_keys2 3
:(省略)
PasswordAuthentication no 4
:(省略)

図8.10 sshd_configの修正箇所


1

追加します。

2 3

コメントアウトされているのでコメントアウトを外します。

4

コメントアウトを外ししてnoにします。

sshdを再起動します。

[armadillo ~]# systemctl restart sshd

これでクライアント側からRSA公開鍵認証でArmadilloにSSHログインできるようになりました。

[PC ~]$ ssh -i ~/.ssh/id_rsa [ArmadilloのIPアドレス]
Enter passphrase for key '/home/atmark/.ssh/id_rsa': 1

[armadillo ~]$

1

秘密鍵にパスフレーズを設定した場合はここで入力します。

8.7.2. 使用するポートを変更する

ここでは通常22であるSSHのポート番号を変更する手順を説明します。 SSHサーバの設定を変更します。

[armadillo ~]# vi /etc/ssh/sshd_config

変更箇所は以下のとおりです。例として8022に変更しています。

:(省略)
Port 8022 1
:(省略)

図8.11 sshd_configの修正箇所(ポート番号)


1

コメントアウトを外し任意のポート番号に変更します。

sshdを再起動します。

[armadillo ~]# systemctl restart sshd

これでポート番号が変更されました。クライアント側からは以下のようにポート番号を指定して接続します。

[PC ~]$ ssh -i ~/.ssh/id_rsa -p 8022 [ArmadilloのIPアドレス]
Enter passphrase for key '/home/atmark/.ssh/id_rsa': 1

[armadillo ~]$

1

秘密鍵にパスフレーズを設定した場合はここで入力します。

[警告]ポート番号

ポート番号はどのプログラムからも使われていない番号を指定してください。

8.8. ソフトウェアアップデート

Armadillo の製品マニュアルの 「特定のイメージファイルだけを書き換える」 に記載されいている方法は、Armadillo に PC などの端末を接続しての操作を必要としています。 製品出荷後でもソフトウェアをアップデートできるような機能をつける場合、 可能な限り簡単な手順でアップデートできる仕組みが望ましいところです。

また、ソースコードの管理も重要です。 これは前述したアップデートの仕組みの前段階として、 「ブートローダーやカーネル等が更新されて新しいソースコードが配布された場合、 古くなったソースコードに行っていたカスタマイズを、 どうやって新しいソースコードに適用するのか」 という話になります。

本章では、 ソースコード管理のためのツールと、 なるべく人手を介さない自動アップデート機能の実現方法を紹介します。

8.8.1. Gitによるソースコードの管理

ソースコードの管理には所謂バージョン管理システムを利用します。 バージョン管理システムには様々ありますが、ここでは Git を使用していきます。

Git を利用すると、 古くなったソースコードに行っていたカスタマイズを、 新しいソースコードに適用することが簡単にできます。 また、バージョン管理システムの基本的な機能として、 ソースコードの変更内容を確認したり、 変更前の状態に戻したりすることもできます。

ここからは、 Armadillo-640 のブートローダーのソースコードを例に、 「古くなったソースコードに行っていたカスタマイズを、 新しいソースコードに適用する」 ための具体的な手順を説明していきます。

流れとしては次のとおりです。

  1. Gitリポジトリの作成
  2. ベースとなるソースコード (u-boot-a600-v2018.03-at7) を登録
  3. ソースコードの修正
  4. ベースとなるソースコードの更新 (u-boot-a600-v2018.03-at7 から u-boot-a600-v2018.03-at8 へ)
  5. 更新後のベースとなるソースコードにソースコードの修正を適用

8.8.1.1. Gitリポジトリの作成

まずは、 https://download.atmark-techno.com/armadillo-640/source/u-boot-a600-v2018.03-at7.tar.gz をATDEにダウンロードして、 tarコマンドで展開します。

[ATDE ~]$ wget https://download.atmark-techno.com/armadillo-640/source/u-boot-a600-v2018.03-at7.tar.gz
[ATDE ~]$ tar xf u-boot-a600-v2018.03-at7.tar.gz
[ATDE ~]$ ls --all -1 u-boot-a600-v2018.03-at7
.
..
.checkpatch.conf
.gitignore
.mailmap
.travis.yml
Documentation
Kbuild
Kconfig
Licenses
MAINTAINERS
Makefile
README
api
arch
board
cmd
common
config.mk
configs
disk
doc
drivers
dts
env
examples
fs
include
lib
net
post
scripts
snapshot.commit
test
tools
[ティップ]

後で新しいソースコードに更新する作業を体験するため、 ここでは、あえて最新ではないソースコードをダウンロードしています。

実際の開発では最新のソースコードをダウンロードしてください。

展開が終わったら、 mv コマンドでディレクトリ名を変更します。 ソースコードのバージョン管理は Git で行えるのでバージョンを除いたディレクトリ名とします。

[ATDE ~]$ mv u-boot-a600-v2018.03-at7 u-boot-a600-v2018.03-at

ディレクトリ名を変更したら、 cd コマンドでディレクトリを移動し、 空のGitリポジトリを作成します。

[ATDE ~]$ cd u-boot-a600-v2018.03-at
[ATDE ~/u-boot-a600-v2018.03-at]$ git init
Initialized empty Git repository in /home/atmark/u-boot-a600-v2018.03-at/.git/

8.8.1.2. ベースとなるソースコードの登録

ベースとなるソースコードをGitリポジトリに登録します。 git add で対象となるファイルを指定し、 git commit で対象のファイルをひとまとまりの修正として登録します。 登録の際は、対象のファイルでどのような修正が行われたか、 を説明するためのコミットメッセージ を記述します。

[ATDE ~/u-boot-a600-v2018.03-at]$ git add -f $(find . -mindepth 1 -maxdepth 1)
[ATDE ~/u-boot-a600-v2018.03-at]$ git commit -m"Imported from u-boot-a600-v2018.03-at7.tar.gz"
[注記]

ここで登録した「ひとまとまりの修正」を Git では コミットと呼びます。

Git はコミット単位でバージョンを管理します。

[注記]

git commit の -m オプションは、 コマンドライン引数でコミットメッセージを記述するためのものです。

-m オプションを指定しなかった場合は自動でエディタが起動するので、 エディタでコミットメッセージを記述します。

[ティップ]

$()は、()内の find . -mindepth 1 -maxdepth 1 を実行し出力された文字列をコマンドライン上に展開します。

git add $(find . -mindepth 1 -maxdepth 1) はカレントディレクトリのファイル全てを git add するという意味になります。 つまり、以下のコマンドを実行するのと同じです。

[ATDE ~/u-boot-a600-v2018.03-at]$ git add ./.git ./tools ./test ./snapshot.commit ./scripts ./post ./net ./lib ./include ./fs ./examples ./env ./dts ./drivers ./doc ./disk ./configs ./config.mk ./common ./cmd ./board ./arch ./api ./README ./Makefile ./MAINTAINERS ./Licenses ./Kconfig ./Kbuild ./Documentation ./.travis.yml ./.mailmap ./.gitignore ./.checkpatch.conf

8.8.1.3. ソースコードの修正

Git でのソースコードの修正は、以下の流れで行います。

  1. 新しいbranch(ブランチ)の作成
  2. ファイルの編集
  3. 編集したファイルのgit add
  4. git commit

先程git commitしたベースとなるソースコードは、 デフォルトのブランチである master に積まれています。 ここから新しいブランチを作成して、 そのブランチ上でソースコードのカスタマイズを行っていきます。 master とブランチを分けることで、 「ベースとなるソースコードの更新」と 「ソースコードのカスタマイズ」を区別することができます。

git log を実行すると現在のブランチに積まれているコミットを確認できます。

[ATDE ~/u-boot-a600-v2018.03-at]$ git log
commit 8c22d30fcb0c254d3f0cf28cb9da4611a7100d70
Author: atmark <atmark@atde7>
Date:   Wed May 13 14:39:35 2020 +0900

    Imported from u-boot-a600-v2018.03-at7.tar.gz

ここから新しいブランチを作成して、 そのブランチをチェックアウトする (現在のブランチからそのブランチに切り替える) には git checkout -b を実行します。 ブランチの名称は製品プロジェクトの名称等で構いません。 ここでは x11a としています。

[ATDE ~/u-boot-a600-v2018.03-at]$ git checkout -b x11a
Switched to a new branch 'x11a'

現在のブランチを確認するには、 git branch を実行します。

[ATDE ~/u-boot-a600-v2018.03-at]$ git branch
  master
* x11a

新しいブランチをチェックアウトしたら、 ソースコードのカスタマイズをしていきます。 ここでは、U-Bootのデフォルトの環境変数に aguide=Software Development を追加していきます。

[ATDE ~/u-boot-a600-v2018.03-at]$ vi include/configs/armadillo-640.h
#define CONFIG_EXTRA_ENV_SETTINGS \
        "setup_mmcargs=setenv bootargs root=/dev/mmcblk0p2 rootwait ${optargs};\0"\
        BOOTCOMMAND_USB\
        "tftpboot=tftpboot uImage; tftpboot 0x83000000 a640.dtb; bootm ${loadaddr} - 0x83000000;\0"\
        STOP_NR3225SA_ALARM_ENV_NAME "=" STOP_NR3225SA_ALARM_DEFAULT ";\0"\
        ENABLE_PF3000_LPM_ENV_NAME "=" ENABLE_PF3000_LPM_DEFAULT "\0"

#define CONFIG_BOARD_LATE_INIT


#endif

図8.12 編集前のinclude/configs/armadillo-640.h(末尾のみ抜粋)


を次のように修正します。

#define CONFIG_EXTRA_ENV_SETTINGS \
        "setup_mmcargs=setenv bootargs root=/dev/mmcblk0p2 rootwait ${optargs};\0"\
        BOOTCOMMAND_USB\
        "tftpboot=tftpboot uImage; tftpboot 0x83000000 a640.dtb; bootm ${loadaddr} - 0x83000000;\0"\
        STOP_NR3225SA_ALARM_ENV_NAME "=" STOP_NR3225SA_ALARM_DEFAULT ";\0"\
        ENABLE_PF3000_LPM_ENV_NAME "=" ENABLE_PF3000_LPM_DEFAULT "\0"\
        "aguide=Software Development\0"

#define CONFIG_BOARD_LATE_INIT


#endif

図8.13 編集後のinclude/configs/armadillo-640.h(末尾のみ抜粋)


編集が終わったら git diff を実行します。 git diff により、 現状のソースコードにどのような変更が行われたのか確認することができます。

[ATDE ~/u-boot-a600-v2018.03-at]$ git diff
diff --git a/include/configs/armadillo-640.h b/include/configs/armadillo-640.h
index 7235241..78ad680 100644
--- a/include/configs/armadillo-640.h
+++ b/include/configs/armadillo-640.h
@@ -121,7 +121,8 @@ int wlan_rtc_i2c_read(void);
        BOOTCOMMAND_USB\
        "tftpboot=tftpboot uImage; tftpboot 0x83000000 a640.dtb; bootm ${loadaddr} - 0x83000000;\0"\
        STOP_NR3225SA_ALARM_ENV_NAME "=" STOP_NR3225SA_ALARM_DEFAULT ";\0"\
-       ENABLE_PF3000_LPM_ENV_NAME "=" ENABLE_PF3000_LPM_DEFAULT "\0"
+       ENABLE_PF3000_LPM_ENV_NAME "=" ENABLE_PF3000_LPM_DEFAULT "\0"\
+       "aguide=Software Development\0"

 #define CONFIG_BOARD_LATE_INIT

正しく修正できていたら、 git add と git commit でコミットを作成します。

[ATDE ~/u-boot-a600-v2018.03-at]$ git add include/configs/armadillo-640.h
[ATDE ~/u-boot-a600-v2018.03-at]$ git commit -m"デフォルトの環境変数にaguideを追加"
[ATDE ~/u-boot-a600-v2018.03-at]$ git log
commit 280d3336a73f0f80269dc166e6653dfcec1a15ca
Author: atmark <atmark@atde7>
Date:   Wed May 13 15:13:38 2020 +0900

    デフォルトの環境変数にaguideを追加

commit 8c22d30fcb0c254d3f0cf28cb9da4611a7100d70
Author: atmark <atmark@atde7>
Date:   Wed May 13 14:39:35 2020 +0900

    Imported from u-boot-a600-v2018.03-at7.tar.gz

8.8.1.4. ベースとなるソースコードの更新

ベースとなるソースコードの更新は、 master ブランチに対して行っていきます。

流れとしては次のとおりです。

  1. master ブランチのチェックアウト
  2. 古くなったソースコードの削除
  3. 新しいソースコードのダウンロード
  4. 新しいソースコードの展開
  5. 新しいソースコードのgit add, git commit

master ブランチをチェックアウトし、 古くなったソースコードの削除していきますが、 その前に git commit していない変更がないかを git status で確認してください。 当然ながら、ファイルを削除すると、 git commit していない変更は失われ、 復元できなくなります。

[ATDE ~/u-boot-a600-v2018.03-at]$ git status
On branch x11a
nothing to commit, working tree clean

ソースコードの削除には git rm を使用します。 git rmはGitリポジトリに登録されているファイルを削除するコマンドです。

それでは、 master ブランチをチェックアウトし、 古くなったソースコードの削除していきます。

[ATDE ~/u-boot-a600-v2018.03-at]$ git checkout master
Switched to branch 'master'
[ATDE ~/u-boot-a600-v2018.03-at]$ git rm -r --ignore-unmatch $(find . -mindepth 1 -maxdepth 1)
[注記]

git rm -r --ignore-unmatch は、指定されたGitリポジトリに登録されているファイルを全て削除する、 という意味になります。

-r オプションでディレクトリ内のファイルを再帰的に削除し、 --ignore-unmatch オプションでGitリポジトリに登録されていないファイルが指定されていても、 コマンドがエラーしないようにしています。

Gitリポジトリ登録されていないファイルがなければ、以下のように ".git" だけがある状態になります。

[ATDE ~/u-boot-a600-v2018.03-at]$ ls --all -1
.
..
.git

次に新しいソースコード (u-boot-a600-v2018.03-at8.tar.gz) をダウンロードし、Gitリポジトリに展開し、 不要なファイルは削除します。

[ATDE ~/u-boot-a600-v2018.03-at]$ wget https://download.atmark-techno.com/armadillo-640/source/u-boot-a600-v2018.03-at8.tar.gz
[ATDE ~/u-boot-a600-v2018.03-at]$ tar xf u-boot-a600-v2018.03-at8.tar.gz
[ATDE ~/u-boot-a600-v2018.03-at]$ ls --all -1 u-boot-a600-v2018.03-at8
.
..
.checkpatch.conf
.gitignore
.mailmap
.travis.yml
Documentation
Kbuild
Kconfig
Licenses
MAINTAINERS
Makefile
README
api
arch
board
cmd
common
config.mk
configs
disk
doc
drivers
dts
env
examples
fs
include
lib
net
post
scripts
snapshot.commit
test
tools
[ATDE ~/u-boot-a600-v2018.03-at]$ mv $(find u-boot-a600-v2018.03-at8 -mindepth 1 -maxdepth 1) ./
[ATDE ~/u-boot-a600-v2018.03-at]$ rmdir u-boot-a600-v2018.03-at8/
[ATDE ~/u-boot-a600-v2018.03-at]$ rm u-boot-a600-v2018.03-at8.tar.gz

新しいソースコードをGitリポジトリに登録します。

[ATDE ~/u-boot-a600-v2018.03-at]$ git add -f $(find . -mindepth 1 -maxdepth 1)
[ATDE ~/u-boot-a600-v2018.03-at]$ git commit -m"Update to u-boot-a600-v2018.03-at8"
[ATDE ~/u-boot-a600-v2018.03-at]$ git log
commit 761d05239a5deeedbba1a915cab099cf31a06b53
Author: atmark <atmark@atde7>
Date:   Wed May 13 16:23:03 2020 +0900

    Update to u-boot-a600-v2018.03-at8

commit 8c22d30fcb0c254d3f0cf28cb9da4611a7100d70
Author: atmark <atmark@atde7>
Date:   Wed May 13 14:39:35 2020 +0900

    Imported from u-boot-a600-v2018.03-at7.tar.gz

これで、ベースとなるソースコードの更新は完了です。

8.8.1.5. 更新後のベースとなるソースコードにソースコードの修正を適用

更新後のベースとなるソースコードに 「ソースコードの修正」 を適用していきます。 この作業は、コンフリクトが起きなければ、 非常に簡単です。

[注記]

Gitが自動でブランチを統合できないことをコンフリクト (conflict) といいます。

流れとしては次のとおりです。

  1. 適用するソースコードの修正を行ったブランチをチェックアウト
  2. git rebaseで更新後のベースとなるソースコードのブランチにリベース
[ATDE ~/u-boot-a600-v2018.03-at]$ git checkout x11a
Switched to branch 'x11a'
[ATDE ~/u-boot-a600-v2018.03-at]$ git rebase master
First, rewinding head to replay your work on top of it...
Applying: デフォルトの環境変数にaguideを追加
Using index info to reconstruct a base tree...
M       include/configs/armadillo-640.h
Falling back to patching base and 3-way merge...
Auto-merging include/configs/armadillo-640.h
Auto packing the repository in background for optimum performance.
See "git help gc" for manual housekeeping.
[ATDE ~/u-boot-a600-v2018.03-at]$ git log
commit 40a9afcdcfc585feb23575338b93ee279a2c2a44
Author: atmark <atmark@atde7>
Date:   Wed May 13 15:13:38 2020 +0900

    デフォルトの環境変数にaguideを追加

commit 761d05239a5deeedbba1a915cab099cf31a06b53
Author: atmark <atmark@atde7>
Date:   Wed May 13 16:23:03 2020 +0900

    Update to u-boot-a600-v2018.03-at8

commit 8c22d30fcb0c254d3f0cf28cb9da4611a7100d70
Author: atmark <atmark@atde7>
Date:   Wed May 13 14:39:35 2020 +0900

    Imported from u-boot-a600-v2018.03-at7.tar.gz

以上で完了となります。

コンフリクトが起きた際は、 コマンド(git rebase) が出力したメッセージに従って修正作業をしていくことになります。 どのような修正を行うかは、その時々で異なるので、 具体的な説明はできません。 修正前のソースコードはどんなものだったのか、 どんな修正なのか、といった視点からその修正が必要なのかを判断し、 修正していく必要があります。

git rebaseでコンフリクトが起き、 とりあえずブランチをもとの状態 (git rebase 前の状態) に戻したい場合は、 git rebase --abort を実行します。

[ATDE ~/u-boot-a600-v2018.03-at]$ git rebase --abort

8.8.2. Armadilloのソフトウェア更新における2つの考え方

Armadilloのソフトウェア更新には、2つの考え方があります。 一つは「イメージファイルのアップデート」、 もう一つは「ソフトウェア単体のアップデート」です。

「イメージファイルのアップデート」は、 ソフトウェアをブートローダー、カーネル、 ユーザーランドに大別してそれぞれをアップデートする、 という考え方です。 これは、Armadillo の製品マニュアルに記載されている 「イメージファイルの書き換え方法」 と同じ考え方です。

「ソフトウェア単体のアップデート」は、 ブートローダー、カーネル、 そしてユーザーランドに含まれるソフトウェアを一つ一つアップデートする、 という考え方です。 この考え方は、ブートローダーとカーネルについては 「イメージファイルのアップデート」 と同じですが、ユーザーランドについては大きく異なります。 アップデートの仕組みは複雑になってしまいますが、 アップデートが必要なソフトウェアのみアップデートできるため、 データ通信量やアップデートにかかる時間を最小限に抑えることができます。

8.8.3. インストールディスクを使用してのアップデート

インストールディスクは、イメージファイルの書き換え方法として、 各製品の製品マニュアルで紹介されています。 「インストールディスクを使用してのアップデート」とは、 インストールディスクを自作し、 自作したインストールディスクを使用して Armadillo のソフトウェアを更新する、 ということです。

大まかな手順は次のとおりです。

  1. ブートローダーイメージのビルド
  2. LinuxカーネルイメージおよびDTBのビルド
  3. ユーザーランドアーカイブのビルド
  4. インストールディスクイメージの作成
  5. インストールディスクの作成
  6. インストールの実行

「ブートローダーイメージの作成」 「LinuxカーネルイメージおよびDTBの作成」 「ユーザーランドアーカイブの作成」 「インストールディスクの作成」 「インストールの実行」 は各製品の製品マニュアルで、 「インストールディスクイメージの作成」 は 「Armadillo標準ガイド Armadillo入門編」 で説明されていますので、 そちらを参照してください。

8.8.4. インターネットを使用してのアップデート

インターネットから最新のソフトウェアをダウンロードしてアップデートする方法を紹介します。

8.8.4.1. APT でインストールしたソフトウェアのアップデート

実は apt コマンドでインストールしたソフトウェア(debian package)については既に、 定期的に最新のソフトウェアをインターネットからダウンロードしてインストールする機能が、 有効になっています。 この機能は、 APT(debian package) に含まれている 複数の Unit ファイルによって実現されています。

この APT の自動アップデート機能が有効になっているかどうかは、 次のようにして確認できます。 apt-daily-upgrade.timer と apt-daily.timer が enabled なら有効になっています。

[armadillo ~]# systemctl list-unit-files | grep apt
apt-daily-upgrade.service              static
apt-daily.service                      static
apt-daily-upgrade.timer                enabled
apt-daily.timer                        enabled

apt の自動アップデート機能を利用するだけであれば、 以上の情報があれば十分ですが、 ここからは、APT の Unit ファイルについて簡単に解説しておきます。 インターネットを使用してのアップデートの実装の一つとして、 参考になるはずです。

[Unit]
Description=Daily apt download activities

[Timer]
OnCalendar=*-*-* 6,18:00  1
RandomizedDelaySec=12h  2
Persistent=true

[Install]
WantedBy=timers.target

図8.14 /lib/systemd/system/apt-daily.timer


1

6時および18時にタイマーが起動するよう設定しています。

2

タイマーの起動を設定時刻から最大12時間後まで、 ランダムに遅らせるよう設定しています。

RandomizedDelaySec の項目は非常に重要です。 Armadilloを量産した際、全てのArmadilloに 図8.14「/lib/systemd/system/apt-daily.timer」 が書き込まれて動作します。 この場合、RandomizedDelaySec の設定をしていなければ、 全てのArmadilloが、 6時または18時ちょうど(つまりほぼ同時)に、 サーバーにアクセスするため、 サーバーに多大な負荷がかかってしまいます。

[Unit]
Description=Daily apt upgrade and clean activities
After=apt-daily.timer  1

[Timer]
OnCalendar=*-*-* 6:00  2
RandomizedDelaySec=60m  3
Persistent=true

[Install]
WantedBy=timers.target

図8.15 /lib/systemd/system/apt-daily-upgrade.timer


1

apt upgrade は、 apt update を実行した後でなければ実行する意味がないため、 apt-daily.timer の後に動作するよう設定しています。

2

6時にタイマーが起動するよう設定しています。

3

タイマーの起動を設定時刻から最大60分後まで、 ランダムに遅らせるよう設定しています。

[Unit]
Description=Daily apt download activities
Documentation=man:apt(8)
ConditionACPower=true
After=network-online.target  1
Wants=network-online.target  2

[Service]
Type=oneshot
ExecStart=/usr/lib/apt/apt.systemd.daily update

図8.16 /lib/systemd/system/apt-daily.service


1

インターネット接続後でなければアップデートできないため、 network-online.target の後に起動するよう設定しています。

2

インターネット接続後でなければアップデートできないため、 network-online.target が起動される場合のみ起動するよう設定しています。

[Unit]
Description=Daily apt upgrade and clean activities
Documentation=man:apt(8)
ConditionACPower=true
After=apt-daily.service  1

[Service]
Type=oneshot
ExecStart=/usr/lib/apt/apt.systemd.daily install
KillMode=process
TimeoutStopSec=900  2

図8.17 /lib/systemd/system/apt-daily-upgrade.service


1

apt upgrade は、 apt update を実行した後でなければ実行する意味がないため、 apt-daily.service の後に動作するよう設定しています。

2

停止の要求を出してから、停止まで 900 秒間は待つよう設定しています。

8.8.4.2. APT でインストールしたソフトウェア以外のソフトウェアのアップデート

APT でインストールしたソフトウェア以外のソフトウェアと言うと、 次のようなソフトウェアが該当します。

  • ブートローダーイメージ
  • Linuxカーネルイメージ
  • DTB(Device Tree Blob)
  • APT 以外のパッケージ管理ソフトウェア[47]でインストールしたソフトウェア
  • C や Python などで自作したアプリケーション
  • アプリケーションの起動を管理するための自作 Unit ファイル

「APT 以外のパッケージ管理ソフトウェアでインストールしたソフトウェア」については、 パッケージ管理ソフトウェアでのアップデートを定期的に実行すれば良いでしょう。 それ以外のソフトウェアについては、 大量にあると制御が大変なので、 tar コマンドで1つに結合したファイルをWebサーバーに配置しておくことにします。

例として、次のような動作を考えます。

  1. 毎日定時に特定のシェルスクリプトを実行する。
  2. シェルスクリプトでは、以下の処理をおこなう。

    1. pip でインストールしたパッケージのアップデート確認
    2. 必要であれば、pip でインストールしたパッケージのアップデート
    3. Webサーバーにアクセスし、更新すべきソフトウェアがあるか確認する。
    4. ファイルがある場合、ダウンロードしてストレージに書き込みアップデート。
    5. 正常に書き込みが完了したら、変更を反映するためにリブート。

毎日定時にスクリプトを実行する処理には、systemdを利用することにします。 systemdについては、 「systemdで定期実行する」 を参照してください。

毎日4:00に処理を実行する場合の設定は以下のようになるでしょう。 ただし、製品を量産した時のことを考慮して RandomizedDelaySec を設定し、 4:00ちょうどには実行しないようにします。 イメージのアップデートを行うスクリプトは、 /root/web_software_update.sh という名前だとします。

[Unit]
Description = web image update
After=network-online.target
Wants=network-online.target

[Service]
ExecStart = /root/web_software_update.sh
Type = oneshot

図8.18 毎日4:00に処理を実行するUnitファイルの実装内容(web_image_update.service)


[Unit]
Description = launch web image updater

[Timer]
Persistent = true
OnCalendar = *-*-* 04:00:00
RandomizedDelaySec=1h

[Install]
WantedBy=timers.target

図8.19 毎日4:00に処理を実行するUnitファイルの実装内容(web_image_update.timer)


systemdから実行される web_software_update.sh は、次のようになります。 pip-review により pip でインストールした Python の各種パッケージをアップデート、 その後、ブートローダー、Linuxカーネル、DTB、アプリケーションについて、 最新バージョンのチェックを行い、 最新バージョンがインストールされていなかった場合は、 ソフトウェアの更新を行ってから Armadillo を再起動させます。 SERVER_IP_ADDRESS(WebサーバーのIPアドレス)、 PROTCOL(httpまたはhttps)、 USER_NAME(認証を行う場合のユーザー名)、 PASSWORD(認証を行う場合のパスワード)等の各変数は、 環境に合わせて書き換えてください。

wgetコマンドは、httpsプロトコルを使用することができます。 また、USER_NAME と PASSWORD 変数を指定することで、 basic認証またはdigest認証を利用できます。

#!/bin/sh

SERVER_IP_ADDRESS=172.16.2.101
PROTCOL='https'
USER_NAME=''
PASSWORD=''

PRODUCT_PATH='products/x11a'
URL_PATH="${PROTCOL}://${SERVER_IP_ADDRESS}/${PRODUCT_PATH}"

BOOTLOADER_IMG_PREFIX='u-boot-a600-v'
BOOTLOADER_IMG_SUFFIX='.imx'
KERNEL_IMG_PREFIX='uImage-a600-v'
KERNEL_IMG_SUFFIX=''
DTB_IMG_PREFIX='armadilo-640-v'
DTB_IMG_SUFFIX='.dtb'
APP_ARCHIVE_PREFIX='app-v'
APP_ARCHIVE_SUFFIX='.tar.xz'

VERSIONSDIR='/var/x11a/versions'
LOCKDIR='/var/lock/auto_image_update'
TMPDIR=''
FILE_IS_UPDATED=false

WGET_OPS=''
if [ "$USER_NAME" ]; then
    WGET_OPS="--http-user=$USER_NAME"
    if [ "$PASSWORD" ]; then
        WGET_OPS="$WGET_OPS --http-password=$PASSWORD"
    fi
fi

log()
{
    logger -p user.$1 -t "$0[$$]" -- "$2"
}

ledctrl()
{
    local LEDCLASS="/sys/class/leds"
    local led="$1"
    shift
    local op="$1"
    shift

    case "${op}" in
        "blink_on")
            local interval="$1"

            echo "timer" > "${LEDCLASS}/${led}/trigger"
            echo $interval > "${LEDCLASS}/${led}/delay_on"
            echo $interval > "${LEDCLASS}/${led}/delay_off"
            ;;
        "blink_off")
            echo "none" > "${LEDCLASS}/${led}/trigger"
            echo 0 > "${LEDCLASS}/${led}/brightness"
            ;;
    esac
}

die()
{
    log err "$1"

    if [ "$TMPDIR" ]; then
        cd
        umount $TMPDIR
        rm -rf $TMPDIR
    fi

    ledctrl red blink_off
    rmdir $LOCKDIR

    exit 1
}

bootloader_update_x1_iotg3()
{
    x1-bootloader-install "$1"
}

kernel_update_x1_iotg3()
{
    mount -t vfat /dev/mmcblk2p1 /mnt && \
    cp "$1" /mnt/uImage && \
    umount /mnt
}

dtb_update_x1_iotg3()
{
    mount -t vfat /dev/mmcblk2p1 /mnt && \
    cp "$1" "$DTB_PATH" && \
    umount /mnt
}

bootloader_update_a600series()
{
    dd if="$1" of=/dev/mmcblk0 \
       bs=1k seek=1 conv=fsync
}

kernel_update_a600series()
{
    mv "$1" /boot/uImage
}

dtb_update_a600series()
{
    mv "$1" /boot/a640.dtb
}

python_update()
{
    # pip3 install pip-review
    pip-review --auto
}

get_latest_filename()
{
    local prefix="$1"
    local suffix="$2"
    local url="$3"
    # for example, if
    #   prefix="uImage-a600-v"
    #   suffix=""
    #   url="https://download.atmark-techno.com/armadillo-640/image/"
    # then get_latest_filename puts
    #   "uImage-a600-v4.14-at21"
    # to stdout as of 2020-04-21.

    wget $WGET_OPS -q -O - "$url" | html2 | grep @href | \
        if [ -n "${prefix}" ]; then
            grep "${prefix}"
        else
            sed '' # pass through
        fi | \
        if [ -n "${suffix}" ]; then
            grep "${suffix}"
        else
            sed '' # pass through
        fi | grep -v ".md5" | \
        sort -V | tail -n 1 | sed -r 's/^.*@href=(.*)$/\1/'
}

check_and_set_version()
{
    local version="$1"

    mkdir -p "${VERSIONSDIR}"

    if find "${VERSIONSDIR}" -mindepth 1 | \
       grep -q "^${VERSIONSDIR}/${version}$"; then
        if tail -n 1 "${VERSIONSDIR}/${version}" | \
           grep -q "^install OK"; then
            return 0 # already installed
        else
            return 1 # previous installation failed
        fi
    fi

    touch "${VERSIONSDIR}/${version}"
    return 2 # then installation start
}

complete_set_version()
{
    echo "install OK" >> "${VERSIONSDIR}/$1"
}

incomplete_set_version()
{
    local version=$1
    shift
    local msg="$@"

    echo "install NG${msg}" >> "${VERSIONSDIR}/$1"
}

download_and_md5sum()
{
    local url_path="$1"
    local filename="$2"

    wget $WGET_OPS -q "${url_path}/${filename}" -O "${filename}" || \
        log err "download ${filename} failed"

    wget $WGET_OPS -q "${url_path}/${filename}.md5" -O "${filename}.md5"
    if [ $? -ne 0 ]; then
        md5sum "${filename}" > "${filename}.md5"
        rm -f "${filename}"

        wget $WGET_OPS -q "${url_path}/${filename}" -O "${filename}"
    fi

    md5sum -c "${filename}.md5"
}

update()
{
    local fail
    local msg
    local update

    python_update

    local LATEST_BOOTLOADER_NAME="$(get_latest_filename "${BOOTLOADER_IMG_PREFIX}" "${BOOTLOADER_IMG_SUFFIX}" "${URL_PATH}")"
    local LATEST_KERNEL_NAME="$(get_latest_filename "${KERNEL_IMG_PREFIX}" "${KERNEL_IMG_SUFFIX}" "${URL_PATH}")"
    local LATEST_DTB_NAME="$(get_latest_filename "${DTB_IMG_PREFIX}" "${DTB_IMG_SUFFIX}" "${URL_PATH}")"
    local LATEST_APP_ARCHIVE_NAME="$(get_latest_filename "${APP_ARCHIVE_PREFIX}" "${APP_ARCHIVE_SUFFIX}" "${URL_PATH}")"

    msg=""
    fail=1
    if [ -n "${LATEST_BOOTLOADER_NAME}" ]; then
        if check_and_set_version "${LATEST_BOOTLOADER_NAME}"; then
            : "nothing to do"
        else
            log info "update bootloader to ${LATEST_BOOTLOADER_NAME}"

            download_and_md5sum "${URL_PATH}" \
                "${LATEST_BOOTLOADER_NAME}" || \
                download_and_md5sum "${URL_PATH}" \
                    "${LATEST_BOOTLOADER_NAME}" # retry
            if [ $? -ne 0 ]; then
                log err "download ${URL_PATH}/${LATEST_BOOTLOADER_NAME} failed"
                msg="${msg}: donwload failed"
            else
                $bootloader_update "${LATEST_BOOTLOADER_NAME}"
                if [ $? -eq 0 ]; then
                    fail=0
                else
                    log err "update ${LATEST_BOOTLOADER_NAME} failed"
                    msg="${msg}: copy failed"
                fi
            fi

            if [ $fail -eq 0 ]; then
                complete_set_version "${LATEST_BOOTLOADER_NAME}"
                FILE_IS_UPDATED=true
            else
                incomplete_set_version "${LATEST_BOOTLOADER_NAME}" "$msg"
            fi
        fi
    fi

    msg=""
    fail=1
    if [ -n "${LATEST_KERNEL_NAME}" ]; then
        if check_and_set_version "${LATEST_KERNEL_NAME}"; then
            : "nothing to do"
        else
            log info "update kernel to ${LATEST_KERNEL_NAME}"

            download_and_md5sum "${URL_PATH}" \
                "${LATEST_KERNEL_NAME}" || \
                download_and_md5sum "${URL_PATH}" \
                    "${LATEST_KERNEL_NAME}" # retry
            if [ $? -ne 0 ]; then
                log err "download ${URL_PATH}/${LATEST_KERNEL_NAME} failed"
                msg="${msg}: donwload failed"
            else
                $kernel_update "${LATEST_KERNEL_NAME}"
                if [ $? -eq 0 ]; then
                    fail=0
                else
                    log err "update ${LATEST_KERNEL_NAME} failed"
                    msg="${msg}: copy failed"
                fi
            fi

            if [ $fail -eq 0 ]; then
                complete_set_version "${LATEST_KERNEL_NAME}"
                FILE_IS_UPDATED=true
            else
                incomplete_set_version "${LATEST_KERNEL_NAME}" "$msg"
            fi
        fi
    fi

    msg=""
    fail=1
    if [ -n "${LATEST_DTB_NAME}" ]; then
        if check_and_set_version "${LATEST_DTB_NAME}"; then
            : "nothing to do"
        else
            log info "update dtb to ${LATEST_DTB_NAME}"

            download_and_md5sum "${URL_PATH}" \
                "${LATEST_DTB_NAME}" || \
                download_and_md5sum "${URL_PATH}" \
                    "${LATEST_DTB_NAME}" # retry
            if [ $? -ne 0 ]; then
                log err "download ${URL_PATH}/${LATEST_DTB_NAME} failed"
                msg="${msg}: donwload failed"
            else
                $dtb_update "${LATEST_DTB_NAME}"
                if [ $? -eq 0 ]; then
                    fail=0
                else
                    log err "update ${LATEST_DTB_NAME} failed"
                    msg="${msg}: copy failed"
                fi
            fi

            if [ $fail -eq 0 ]; then
                complete_set_version "${LATEST_DTB_NAME}"
                FILE_IS_UPDATED=true
            else
                incomplete_set_version "${LATEST_DTB_NAME}" "$msg"
            fi
        fi
    fi

    msg=""
    fail=1
    if [ -n "${LATEST_APP_ARCHIVE_NAME}" ]; then
        if check_and_set_version "${LATEST_APP_ARCHIVE_NAME}"; then
            : "nothing to do"
        else
            log info "update applications by extracting ${LATEST_APP_ARCHIVE_NAME}"

            download_and_md5sum "${URL_PATH}" \
                "${LATEST_APP_ARCHIVE_NAME}" || \
                download_and_md5sum "${URL_PATH}" \
                    "${LATEST_APP_ARCHIVE_NAME}" # retry
            if [ $? -ne 0 ]; then
                log err "download ${URL_PATH}/${LATEST_APP_ARCHIVE_NAME} failed"
                msg="${msg}: donwload failed"
            else
                tar xf "${LATEST_APP_ARCHIVE_NAME}" -C /
                if [ $? -eq 0 ]; then
                    fail=0
                else
                    log err "update ${LATEST_APP_ARCHIVE_NAME} failed"
                    msg="${msg}: extract archive failed"
                fi
            fi

            if [ $fail -eq 0 ]; then
                complete_set_version "${LATEST_APP_ARCHIVE_NAME}"
                FILE_IS_UPDATED=true
            else
                incomplete_set_version "${LATEST_APP_ARCHIVE_NAME}" "$msg"
            fi
        fi
    fi
}

model="$(cat /proc/device-tree/model)" > /dev/null
case "$model" in
    *"Atmark Techno Armadillo-6"*)
        bootloader_update=bootloader_update_a600series
        kernel_update=kernel_update_a600series
        dtb_update=dtb_update_a600series
        case "$model" in
            *"Armadillo-64"*)
                DTB_PATH="/boot/a640.dtb"
                ;;
            *"Armadillo-61"*)
                DTB_PATH="/boot/a610.dtb"
                ;;
            *)
                die "unknown model($model)"
                ;;
        esac
        ;;
    *"Atmark-Techno Armadillo-X1"*"miniaml"*)
        die "detect minimal model($model)"
        ;;
    *"Atmark-Techno Armadillo-X1"*|*"Atmark-Techno Armadillo-IoT"*"G3"*)
        bootloader_update=bootloader_update_x1_iotg3
        kernel_update=kernel_update_x1_iotg3
        dtb_update=dtb_update_x1_iotg3
        case "$model" in
            *"Armadillo-X1 Board"*)
                DTB_PATH="/mnt/armadillo_x1.dtb"
                ;;
            *"Armadillo-X1L Board"*)
                DTB_PATH="/mnt/armadillo_x1l.dtb"
                ;;
            *"Armadillo-IoT"*"G3 Board"*)
                DTB_PATH="/mnt/armadillo_iotg_g3.dtb"
                ;;
            *"Armadillo-IoT"*"G3 M1 Board"*)
                DTB_PATH="/mnt/armadillo_iotg_g3_m1.dtb"
                ;;
            *"Armadillo-IoT"*"G3 W2 Board"*)
                DTB_PATH="/mnt/armadillo_iotg_g3_w2.dtb"
                ;;
            *)
                die "unknown model($model)"
                ;;
        esac
        ;;
    *)
        die "unknown model($model)"
        ;;
esac

mkdir -p $LOCKDIR || die "making LOCKDIR($LOCKDIR) is failed"

ledctrl red blink_on 100

TMPDIR=$(mktemp -d /tmp/web_software_update.XXXXXXXX)
if [ -z "$TMPDIR" ]; then
    die "mktemp failed"
fi

cd $TMPDIR

update

cd
rm -rf $TMPDIR


ledctrl red blink_off

rmdir $LOCKDIR

if [ "$FILE_IS_UPDATED" = true ]; then
    log info "reboot"
    reboot
fi

図8.20 Webサーバーにあるイメージファイルでフラッシュメモリをアップデートするスクリプト(web_software_update.sh)


[注記]

html2 コマンドを使用するには、 xml2 をインストールしておく必要があります。

[armadillo ~]# apt install xml2

8.9. PCとArmadillo間でファイルを共有する

Armadillo上で動くプログラムを開発する場合、 開発作業自体はPC上やあるいはPC上で動いている ATDE上で行うことが多いです。

PC上で作成したプログラムを動作確認する場合、 Armadilloへ転送する必要がありますが、 動作確認のたびにscpなどでファイル転送するのは 大変です。 そのような煩わしさを解決する方法の1つとして PCとArmadillo間でのファイル共有があります。

ここでは、sambaを使ったファイル共有の方法について 説明します。

8.9.1. sambaをインストールする

sambaはaptでインストールできます。

[armadillo ~]# apt install samba

8.9.2. sambaを設定する

まず、共有ディレクトリへアクセスできるユーザを追加します。 このユーザはLinux上に存在しているユーザでなければなりません。 新たなユーザを追加したい場合は「ユーザーの追加と削除」を 参考にLinuxにユーザを追加してください。

ここではLinux上のユーザであるatmarkを共有ディレクトリにアクセスできる ユーザとして追加します。追加にはpdbeditコマンドを使います。

passwordには共有ディレクトリにアクセスするためのパスワードを設定してください。

[armadillo ~]# pdbedit -a atmark
new password:
retype new password:
Unix username:        atmark
NT username:
Account Flags:        [U          ]
User SID:             S-1-5-21-1214478273-341451916-2061574801-1001
Primary Group SID:    S-1-5-21-1214478273-341451916-2061574801-513
Full Name:
Home Directory:       \\armadillo\atmark
HomeDir Drive:
Logon Script:
Profile Path:         \\armadillo\atmark\profile
Domain:               ARMADILLO
Account desc:
Workstations:
Munged dial:
Logon time:           0
Logoff time:          never
Kickoff time:         never
Password last set:    Fri, 08 May 2020 12:45:59 JST
Password can change:  Fri, 08 May 2020 12:45:59 JST
Password must change: never
Last bad password   : 0
Bad password count  : 0
Logon hours         : FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF

次に共有ディレクトリを作成します。共有ディレクトリは /home/atmarkの下に作成し、所有ユーザとグループを atmarkに変更します。

[armadillo ~]# mkdir /home/atmark/share
[armadillo ~]# chown atmark:atmark /home/atmark/share

次にsambaの設定ファイルを修正し、作成したディレクトリを共有します。

/etc/samba/smb.confファイルの末尾に以下を追記します。

[armadillo ~]# vi /etc/samba/smb.conf
(省略)

[Share] 1
   path = /home/atmark/share 2
   read only = no 3
   guest only = no 4
   guest ok = no 5

1

任意の共有名称です。Shareでなくてもかまいません。

2

共有ディレクトリのパスです。

3

読み取り専用とはしません。

4

ゲストユーザのアクセスを不可にします。

5

ゲストユーザのアクセスを不可にします。

最後にsambaを再起動します。

[armadillo ~]# systemctl restart smbd nmbd

ここまでで、基本的なsambaの設定は完了です。

sambaの設定ファイルにはここで紹介したものの 他にも数多くの設定項目があります。 詳細はSambaユーザ会のサイトを参照してください。 [48]

8.9.3. Windowsからアクセスする

前章でArmadillo上に作成したsambaの共有ディレクトリに Windowsからアクセスするには、ネットワークドライブの割り当てをします。

ネットワークドライブの割り当てをする際に表示される「フォルダー」入力欄に 以下のようにArmadilloのIPアドレスと共有ディレクトリ名を入力します。

\\<ArmadilloのIPアドレス>\share

次に、ユーザ名とパスワードの入力を求められるので、「sambaを設定する」 で設定したユーザ名(ここではatmark)とパスワードを入力します。

これでWindowsからshareディレクトリに対してファイルの読み書きができるようになります。

8.9.4. ATDEからアクセスする

共有ディレクトリにATDEからアクセスするにはmountコマンドで 共有ディレクトリを任意の場所にマウントします。

[ATDE ~]$ mkdir share 1
[ATDE ~]$ sudo mount -t cifs -o username=<ユーザ名>,password=<パスワード> //<ArmadilloのIPアドレス>/share share 2

1

マウントするためのディレクトリを作成します。

2

作成したディレクトリにマウントします。

マウントしたのちに、shareディレクトリに移動しファイルを作成すると、 Armardillo側からもファイルを確認できます。

このように、sambaによるファイル共有を行うことで、 PCとArmadillo間でのファイルのやり取りがスムーズになり、 開発効率の向上につなげることができます。



[45] この操作を、 ウォッチドッグタイマーをキックする、撫でる、起こす、などと表現します。

[46] Armadillo-400シリーズや Armadillo-800シリーズ等で採用していた Atmark Distでは、syslogデーモンとしてBusyBoxのsyslogdを使用していました。 BusyBoxのsyslogdは機能が基本的なものに限られているため、設定ファイルを 持たず、設定はすべてコマンドラインオプションで指定します。

[47] pip(Python) や gem(Ruby) が該当します。