Debianが起動しない場合の復旧方法 (Bug#932935)

2019年7月28日に Debianが起動しなくなりました.原因は sid(unstable)と呼ばれる開発版のみに存在するバグです.

バグレポートも出ています.症状としては,システムを再起動すると" logsave not found - requires manual a fsck "と表示されるだけでシステムが起動しなくなる,という恐ろしいものになります.

Debian Bug report logs - #932935 System refuses to boot, logsave not found - requires manual a fsck
https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=932935

このバグはすでに修正されています.しかし不幸にも debianが起動しなくなった場合の対処方法として,復旧手順をメモしておきます.

復旧手順の概要

パッケージのバグが原因でブートしない場合は,バグ修正版のパッケージでアップグレード(つまり上書きインストール)すれば復旧できます.

その手順は

  1. 無理やり手動でシステムを起動
  2. IPアドレスを手動設定
  3. 無理やり apt update して apt install

となります

この手順は,知っておくと結構役に立つので,以下詳しく手順を説明します*1

手順1: 状況の確認

Linuxの起動に失敗すると,次のような画面が表示されます.

Busybox バージョン情報
Enter 'help' for a list of built-in commands.

(initramfs)

この画面では,ディスクのエラーチェックだとか,設定ファイルの修正,などが行えます.今回は手動で無理やりシステムを起動させます.

手順2: IPアドレスの付与

まず余っているIPアドレスを手動設定します

(initramfs) ifconfig eth0  IPアドレス

次にデフォルトゲートウェイを手動設定します

(initramfs) route add default gw ルーターのIPアドレス

手順3: システムの root (/) のマウント

まずマウントポイントを作成します./tmp 以下に作るのが無難だと思います.

(intiramfs) mkdir /tmp/root

ディスクをマウントします.カーネルモジュールを手動でロードする必要があります.例えば ext4 でフォーマットしている場合は,次のようなコマンドを実行します.

(intiramfs) modprobe ext4
(initramfs) mount -t ext4 /dev/デバイス  /tmp/root

これでマウントできるはずです.確認は df コマンドを使います

(initramfs) df

手順4: システムの起動

chroot でディスク上のシステムに切り替えます.

(initramfs) chroot /tmp/root 

本来なら /proc や /dev などをマウントしてから chroot すべきですが,今回は緊急事態なので,細かいことは省略します.

いくつか警告が出ますがそれも無視

手順5: 確認

これで bashに切り替わるはずです.apt コマンドが起動できるか確認します.

$ apt 

問題なければ apt update します

$ apt  update

ネットワークの設定が合っていれば,update が始まるはずです.これでバグ修正済みのパッケージ情報がgetできます

手順6: バグ修正版パッケージの上書きインストール

上記bugレポートに纏められているように,今回システムが起動しなくなった原因は e2fsprogs というパッケージのバグにあります.すでにバグは修正済みなので,修正済みのe2fsprogs を apt で上書きインストールします

$ apt install e2fsprogs 

これで作業は終わりです.再起動すればシステムは元どおり正常起動するようになります.

手順7: それでも復旧できない場合

バグの原因が特定できない場合は,とりあえず更新済みパッケージを全部インストールする,つまり

$ apt upgrade

するという方法もあります.

ただし,/dev や /procを準備してない状態で apt upgrade をすると想定外の事故が起こる可能性もあります.個人的には apt upgrade は最後の手段にとっておいたほうが良いとおもいます.

*1:ブートローダーであるgrub2 ,initramfsの中にあるシェル busybox が壊れていない限りはまずこの方法でシステムは復元できます

macOS に Tex Live 2019をインストールする

macOS mojave の TeX環境を texlive2019 に更新したので手順をまとめます.

この記事は以下のエントリを macOS mojaveと Tex Live 2019 に合わせて更新・加筆したものです.

BasicTeX

パッケージはMacTeXのサブセット版である BasicTeX を使いました.

MacTeXは全パッケージをインストールします.そのため 2.4GB ものディスク容量を消費します (Tex live 2014の場合).

一方サブセット版である BasicTeX は最低限のパッケージしかインストールしません.必要な容量は 248MB程度です.不足パッケージは後から簡単にインストールできるので実用上の問題もありません

ダウンロード&インストール

https://tug.org/mactex/morepackages.html から mactex-basic.pkg をダウンロード,インストールします

なおMacTeXは

  • 2019 通常版 /usr/local/texlive/2019
  • 2019 BasicTeX /usr/local/texlive/2019basic
  • 2018 BasicTeX /usr/local/texlive/2018basic

という感じでディレクトリを分けてファイルを配置します

デフォルトのTeX環境の選択は,"TeX Distribution"というアプリで行います.
(メニューの”システム環境設定"にアイコンがあります.Spotlight検索で TeX Distribution でも起動できます)
このアプリは

  • /Library/TeX/texbin
  • /Library/TeX/Root
  • /Library/TeX/Local
  • /Library/TeX/Documentation

というシンボリックリンクを管理していて,これらリンクでデフォルトのtex環境を選択する仕組みになっています.

たとえば mactex-basic.pkg をインストールすると,/Library/TeX/texbin のリンク先は

$ readlink -f  /Library/TeX/texbin
/usr/local/texlive/2019basic/bin/x86_64-darwin

という感じで,/usr/local/texlive/2019basic/bin/x86_64-darwin になります

初期設定

basictex をインストールしたら,必要なパッケージを追加インストールします

方法は二つ

  • Tex Live ユーティリティーというGUIを使う方法
  • CUIでtlmgrコマンドを使う方法

あります.

ここでは,説明が簡単,という理由でCUIを使います.

まずターミナルを起動します.そして以下のコマンドを実行します.これで tlmgr 自身を更新します

$ sudo tlmgr update --self 

次にインストール済みパッケージの更新

$ sudo tlmgr update  --all

最後に,個人的に良く使うパッケージを追加インストールします.

$ sudo tlmgr install  type1cm subfigure dvipdfmx multirow xstring logreq biblatex latexmk  biber 

日本語環境の設定

Tex live 2018同様の方法で日本語の設定を行います.

TLContirb リポジトリの追加登録

ライセンスの関係で Tex live 2018 でパッケージ構成に変更がありました.

macOS 同梱のフォントを使うためのパッケージ達は別リポジトリ( TLContrib )へ分離されたので,
まずはこのリポジトリを登録します.

$ sudo tlmgr repository add http://contrib.texlive.info/current tlcontrib
$ sudo  tlmgr pinning add tlcontrib '*'


フォント周りで重要なパッケージは

  • cjk-gs-integrate-macos (cjk-gs-integrateのmacOS版,フォントの設定を行うスクリプトです)
  • japanese-otf-nonfree japanese-otf-uptex-nonfree ptex-fontmaps-macos

です

リポジトリの登録が済んだら,日本語関連のパッケージをまとめてインストールします

$ sudo tlmgr  install japanese-otf-nonfree japanese-otf-uptex-nonfree  ptex-fontmaps-macos cjk-gs-integrate-macos ptex platex jsclasses japanese-otf cjk-gs-integrate adobemapping ptex-fontmaps
フォントの登録

cjk-gs-integrate-macos というスクリプト(cjk-gs-integrate のmac os 版)がインストールされるので,これを使って mac OS のフォントを登録します

$ sudo cjk-gs-integrate --link-texmf --cleanup
$ sudo cjk-gs-integrate-macos --link-texmf --force
$ sudo mktexlsr
登録ずみフォントの確認

フォントの設定用に kanji-config-updmap-sys というスクリプトがインストールされます.

このスクリプトで現在の設定を確認します.

$ sudo kanji-config-updmap-sys status

以下の出力が得られます

CURRENT family for ja: ipaex
Standby family : hiragino-highsierra
Standby family : hiragino-highsierra-pron
Standby family : ipa
Standby family : kozuka
Standby family : ms
Standby family : yu-win

先頭行は現在の設定で,日本語フォントは埋め込まない,という意味です
残りは利用可能なフォントの一覧です

フォントの変更

フォントが登録できたら,pdfに埋め込むフォントを指定します

$ sudo kanji-config-updmap-sys --jis2004 hiragino-highsierra-pron

自動で extractbb が実行されるようにする

以下の内容で /usr/local/texlive/texmf-local/web2c/texmf.cnf を用意します

shell_escape_commands = \
bibtex,bibtex8,bibtexu,upbibtex,biber,\
kpsewhich,\
makeindex,mendex,texindy,xindy,\
mpost,upmpost,\
repstopdf,epspdf,extractbb

古い TeX環境の削除

uninstallは簡単です

MacTex 2018 を消したい場合は /usr/local/texlive/2018basic をディレクトリ毎削除するだけです

$ sudo rm -rf /usr/local/texlive/2017basic

トラブルシューティング

pdfに画像が貼れない/gsコマンドが無い
ghostscript (gsコマンド) がインストールされていないと dvipdfmx 経由で作成する pdf に画像が貼れない場合があります.ghostscript をインストールするには https://tug.org/mactex/morepackages.html の mactex-additions.pkg から ghostscriptを選択してインストールするのが楽そうです.
LaTeX It を使いたい
gsコマンドが必要なので,あらかじめ上記の手順でghostscriptをインストールして http://www.chachatelier.fr/latexit/ からdmgをダウンロード,インストールする.
TeX Live Utility.app を使いたい
https://code.google.com/p/mactlmgr/
dvipdfmx がCould not find encoding file “H”. というエラーを出す
adobemapping パッケージをインストールすると直ります
日本語フォントが使えなくなった
Tex live 2018 でフォント周り(パッケージ構成やコマンド名)に少し変更が入っているようです.上記手順を参考にフォント周りの設定を見直してください

まとめ

インストール後の /usr/local/texlive/2019basicのサイズは 586MBでした

参考までに

  • /usr/local/texlive/2018basicのサイズは 546MB
  • /usr/local/texlive/2018basicのサイズは 766MB

でした.

Tex live 2014は全部インストールすると 2.4GB程度なので,それに比べるとかなりディスク容量を節約できたようです.






[改訂第7版]LaTeX2ε美文書作成入門
奥村 晴彦 黒木 裕介
技術評論社
売り上げランキング: 4,008


学生・研究者・技術者のためのLATEXを用いた論文作成術
渡辺 徹
プレアデス出版
売り上げランキング: 233,232

迷惑メール対策に負けない,exim4 の正しい設定方法

ラズパイやPC Linuxで構築したサーバ機では, ログの監視結果などをメール送信できると格段に使い勝手が向上します.

しかし今のインターネットは迷惑メール対策がしっかりしています.例えば

  • プロバイダのOutbound Port 25 Blocking
  • SPFによる送信ドメイン認証(SPF

などの迷惑メール対策があり,これらの対策を突破しないとメールは相手に届きません.

迷惑メール対策に負けない Linux の設定

と言うわけで,これら迷惑メール対策に負けない設定として, exim4を使う方法を説明します.

ポイントは三つあります.

  1. exim4を使います
    • 軽量です
    • ラズパイのような組み込みシステム,利用形態で問題なく利用できます.
  2. 外部のメールサーバ(SMTPサーバー)経由で送信します
    • 外部のSMTPサーバーとして,以下のサーバーを使ってメールを送信します.
    • SPF対策済みのSMTPサーバーを使用するので,メールがSPAMと誤判定されにくくなります.
  3. SSL使います
    • SMTP authを使うので,SSLでユーザ名,パスワードを保護します.

exim4の基本設定

基本的な設定は dpkg-reconfigure で行うのが楽です.

$ sudo dpkg-reconfigure exim4-config

慣れている人は /etc/exim4/update-exim4.conf.conf を直接編集してもOKです.


設定で大事なのは smarthost の設定です.smarthost で中継先のSMTPサーバを指定します.書式は

SMTPサーバ名::ポート番号

です.区切りは":"ではなくて "::" とコロンを2回連続で書くことに注意してください.

googleSMTPを使うなら

smarthost = "smtp.gmail.com::465"

となります

SMTP-authのパスワード設定

/etc/exim4/passwd.client にパスワードを記入します

*:アカウント名:パスワード

"*"の部分はSMTPサーバーの名前です.今回の設定方法では,SMTPサーバーは一つしか設定しないので,"*"と書いておきましょう.

パスワードは平文で記入します.

心配な人は /etc/exim4/passwd.client のパーミッションを確認しましょう.デフォルトでroot以外はファイルの中身を閲覧できないようなっているはずです(一般ユーザでpasswd.clientの中身が読めたらセキュリティホールです.開発者にバグレポートを出しましょう)

exim4の設定ファイルの更新

/etc/exim4/exim4.conf.localmacros の末尾に以下の行を追加します

    MAIN_TLS_ENABLE = 1 
    REMOTE_SMTP_SMARTHOST_HOSTS_REQUIRE_TLS = *
        TLS_ON_CONNECT_PORTS = 465 
    REQUIRE_PROTOCOL = smtps 

続けて /etc/exim4/exim4.conf.template の .ifdef REMOTE_SMTP_SMARTHOST_HOSTS_REQUIRE_TLS ... .endif の部分に以下の行を追加します

    .ifdef REQUIRE_PROTOCOL
        protocol = REQUIRE_PROTOCOL 
    .endif 

さらに下記3行を /etc/exim4/exim4.conf.template の .ifdef MAIN_TLS_ENABLE の次の行に追加します.

    .ifdef TLS_ON_CONNECT_PORTS
        tls_on_connect_ports = TLS_ON_CONNECT_PORTS 
    .endif 

設定の反映と exim4 の再起動

$ sudo  /usr/sbin/update-exim4.conf
$ sudo  /etc/init.d/exim4 restart

動作確認

配信経路確認

/usr/sbin/exim4 を使うと,メールの配信経路を確認できます.

$ /usr/sbin/exim4 -bt  hogehoge@example.com
R: smarthost for hogehoge@example.com
hogehoge@example.com
  router = smarthost, transport = remote_smtp_smarthost
  host 外部SMTPサーバ名 [IPアドレス] port=465
  • smarthostの設定で,外部SMTPサーバーに中継されていること
  • 465ポートに送信していること

が確認できます

送信テスト

mailコマンドでメールを送信してみましょう

$ echo "hogehoge" | mail -s test   hogehoge@example.com

トラブルシューティング

ログ

エラーなどはすべてログファイルに出力されます.うまく行くときも,うまく行かないときも,ログファイルを見るのが基本です.

$ sudo tail -f /var/log/exim4/maillog
メールキューを全削除
$ sudo -s
 exim -bp | exiqgrep -i | xargs exim -Mrm

Linuxカーネルのソースコードを読む方法(その4: list.h 後編)

Linux カーネルソースコードを読んだのでメモを公開します.Linuxカーネルを読む際の参考になれば幸いです.

お題: list.h

前回の続き,リスト構造の実装です.

前回のエントリはこちら http://pyopyopyo.hatenablog.com/entry/2019/06/30/150000

リストの操作: ノードの初期化

ノードの初期化は INIT_LIST_HEAD()を使います.

    struct list_head head;
    INIT_LIST_HEAD(&head);

INIT_LIST_HEAD() で初期化されると,ノードのポインタ,head.prev と head.next の値は次のようになります.

   +-----------------------+
   | prev             prev |
   +---  +---------+ <-----+
         |  head   |
   +---> +---------+  ---> +
   |                  next |
   +-----------------------+

INIT_LIST_HEAD()は include/linux/list.h の26行目で次のように実装されています.

26:
static inline void INIT_LIST_HEAD(struct list_head *list)
{
	WRITE_ONCE(list->next, list);
	list->prev = list;
}

WRITE_ONCEはマクロで,WRITE_ONCE(A,B) で変数Aに,値Bをアトミックに代入するインラインアセンブラに展開されます.

アトミック処理を無視すると,INIT_LIST_HEADは下のコードと等価になります.

static inline void INIT_LIST_HEAD(struct list_head *list)
{
        list->next = list; // 実際には,この代入はアトミック処理
        list->prev = list;
}

ここで少し話が脱線しますが,重要な点があります.それは,

  • なぜアトミック処理が必要になるのか?
  • なぜWRITE_ONCE()はnext ポインタのみで,prevポインタは使用されていないのか?

の2点です.

まず1つ目,アトミック処理が必要な理由は「struct list_head が複数のCPUから同時にアクセスされる場合があるから」です.
たとえば list のアドレスが 0x11223344 だったとします.

この場合

list->next = list 

というCのコードは,次のようにコンパイルされる場合があります(アセンブラC言語風に記述します.)

レジスタ0 = list->nextのアドレス
レジスタ1 = 0x1122;
レジスタ2 = 0x3344;
*(レジスタ0) = レジスタ1;
*(レジスタ0+1) = レジスタ2;

このようにC言語では一行の処理でも,実際にはアドレス上位とアドレス下位の2つの転送命令にコンパイルされる場合があります.
上記の例だと,list->next のポインタ変数は,4行目と5行目の2命令で更新されます.

このように2命令で nextのポインタ変数を書き換えてしまうと,その書き換え中,つまり4行目が実行されて,5行目が実行される前の期間は,nextポインタのアドレスが無効な値になります.無効な値になるのは一瞬ですが,その瞬間にハードウェア割り込みやプリエンプション(ソフトウェア割り込み)で別の処理が割り込んだ場合,その割り込み処理は 無効なnextアドレスを参照してしまいます.結果カーネルがクラッシュします.

そこでLinuxカーネルは, struct list_head の操作に対して以下のルールを儲けてアトミック性を保証するようになっています.

  • nextのアクセスはアトミック処理に実装.具体的には WRITE_ONCE(),READ_ONCE()を使う
  • アトミック処理が必要な場面では next のみを使用し, prev は用いない

前述の2つ目の疑問,WRITE_ONCE()がnextのみで prevには使用されていない理由もこれで説明できます.つまり,このルールに従って INIT_LIST_HEAD()が実装されているから,ということになります.

リストの操作: list_empty()

list_empty()は,リストが空である場合 true を返す関数です.

実装は list.hの235行目です

static inline int list_empty(const struct list_head *head)
{
	return READ_ONCE(head->next) == head;
}

前述のようにnextポインタへのアクセスはアトミックであるべきなので,この関数はREAD_ONCEマクロを使用して実装されています.意味的には以下の処理をアトミックに行います.

static inline int list_empty(const struct list_head *head)
{
	return head->next == head;
}

リストの操作: list_add()

ノードをリストに追加する関数です list.h の69行目で実装されています.

69:
/**
 * list_add - add a new entry
 * @new: new entry to be added
 * @head: list head to add it after
 *
 * Insert a new entry after the specified head.
 * This is good for implementing stacks.
 */
static inline void list_add(struct list_head *new, struct list_head *head)
{
	__list_add(new, head, head->next);
}

実体は __list_addです

50:
/*
 * Insert a new entry between two known consecutive entries.
 *
 * This is only for internal list manipulation where we know
 * the prev/next entries already!
 */
static inline void __list_add(struct list_head *new,
			      struct list_head *prev,
			      struct list_head *next)
{
	if (!__list_add_valid(new, prev, next))
		return;

	next->prev = new;
	new->next = next;
	new->prev = prev;
	WRITE_ONCE(prev->next, new);
}

ここで WRITE_ONCE()の使い方に注意すると

  • 新しく追加するノード new は,まだリストに登録されていないので他のCPUはアクセスできない WRITE_ONCE()は不要
  • 既存のノード prev->next は,他のCPUがアクセスする可能性があるので WRITE_ONCE()を使用

となっています.つまりこの list_add()は排他制御なしで安全に利用できます.

まとめ

  • list.h を読みました
  • リストは Doubly circular linked list を実装していました.
    • ノードは struct list_head; ポインタとして prev と next を持っています
    • リストは出来るだけロックフリーで使えるように工夫されています.
    • リストへのアクセスはアトミックになるように気をつけましょう.
  • API
    • データ構造 struct list_head;
      • 構造体へのアクセスはマクロ container_of() を使う
    • 初期化
      • INIT_LIST_HEAD()
    • 追加
      • list_add()
    • 走査
      • list_for_each()
      • list_for_each_entry()
    • 他にもたくさんマクロや関数が定義されています.詳しいことは include/linux/list.h を直接読みましょう.

Linuxカーネルのソースコードを読む方法(その3: list.h 前編)

Linux カーネルソースコードを読んだのでメモを公開します.Linuxカーネルを読む際の参考になれば幸いです.

今回のお題: include/linux/list.h

今回はカーネル内部で多用されているデータ構造の一つとして linked list の実装を読みました.主なファイルは linux/list.h になります.ソースコードのバージョンは linux-5.1.8です.

カーネル内部でよく利用されているデータ構造

よく利用されているデータ構造としては以下のものがあります.

  • List
  • Queue
  • Map
  • Binary tree

C++であればSTLの std::list とか std::queue を利用するところです.しかしカーネルの実装しかもC言語です. linux kernel は自前の実装を提供しています.

Singly linked list と Doubly circular linked list

今回読むソースコード include/linux/list.h はいわゆる doubly circular linked list を実装しています.

念の為 Singly linked list と Doubly circular linked list の違いを説明しておきます.

singly linked list

リスト構造には様々な種類があります.例えば singly linked list であれば,ノードを

struct Node {
    struct Node *next;
};

のような形で定義します.そしてこのNode構造体を使ってリスト構造を構築します.アスキーアートで図示すると,例えばノード数3のリスト構造は次のようになります.リストの終端はNULLポインタで表現しています

         +---------+       +---------+       +---------+ 
         |  Node   |       |  Node   |       |  Node   |       
         +---------+  ---> +---------+  ---> +---------+  ---> NULL
                     next             next            
doubly circular linked list

一方,linux/list.hが実装している doubly circular linked list では,ノードを次のように定義します.

struct list_head {
	struct list_head *next;
        struct list_head *prev;
};

実際の定義は include/linux/types.h の186行目にあります.

そしてリスト構造を次のように構築します.こちらもノード数3のリスト構造の例です.

   +--------------------------------------------------------+
   | prev             prev             prev                 |  
   +---  +---------+ <--   +---------+ <--   +---------+ <--+  
         |list_head|       |list_head|       |list_head|       
   +---> +---------+  ---> +---------+  ---> +---------+  --+
   |                  next             next            next |
   +--------------------------------------------------------+

singly linked list と異なり doubly linked list には nextのポインタに加えて prevのポインタがあります.この2つのポインタを使って,各ノードは,次ノード,前ノード,双方にリンクを貼っています.

また circular linked list とあるように,リストの終端がありません.リストの末尾はリストの先頭に接続します.

struct list_head の利用例

// ノードの定義
struct Data {
    struct list_head list;
    int value;
};

// ノードを新しく生成する関数
struct Data * new_data()
{
    Data *ptr = kalloc( sizeof (struct Data), GFP_KERNEL );
    INIT_LIST_HEAD(&ptr->list);
    return ptr;
}

// リストを作成する例
void myfunc {
    struct list_head head;
    INIT_LIST_HEAD(&head);
    
    struct Data* A = new_data();
    struct Data* B = new_data();
    list_add(&A->list,  &head);
    list_add(&B->list,  &head);
}

myfunc() の中で生成したリスト構造をアスキーアートで表現すると次のようになります.

   +--------------------------------------------------------+
   | prev             prev             prev                 |  
   +---  +---------+ <--   +---------+ <--   +---------+ <--+  
         |   head  |       | A->list |       | B->list |       
   +---> +---------+  ---> +---------+  ---> +---------+  --+
   |                  next             next            next |
   +--------------------------------------------------------+

リストの操作: list_for_each()

circular linked list にはリストの末尾がありません.foreach()のような,リストの要素を順番に見る処理は次のように実装します.

struct list_head *pos;
for (pos = &head->next; ptr != &head; ptr = ptr->next) {
    // 処理
}

linux カーネルは list_for_each() というマクロを定義しています.(list.hの489行目)

#define list_for_each(pos, head) \
	for (pos = (head)->next; pos != (head); pos = pos->next)

このマクロを使うと上記のコードは

  struct list_head *pos;
  list_for_each(pos, &head) {
      // 処理
  }

という風にコーディングできます.

リストの操作: container_of()

list_for_each は list_head を順に走査するマクロです. list_head から strcut Data のポインタを得るには container_ofマクロを使います

  struct list_head *pos;
  list_for_each(pos, &head) {
       struct Data * p = container_of(pos, struct Data, list);
  }

container_of マクロは linux/kernel.h の970行目で定義されているマクロです.

970:
/**
 * container_of - cast a member of a structure out to the containing structure
 * @ptr:	the pointer to the member.
 * @type:	the type of the container struct this is embedded in.
 * @member:	the name of the member within the struct.
 *
 */
#define container_of(ptr, type, member) ({				\
	void *__mptr = (void *)(ptr);					\
	BUILD_BUG_ON_MSG(!__same_type(*(ptr), ((type *)0)->member) &&	\
			 !__same_type(*(ptr), void),			\
			 "pointer type mismatch in container_of()");	\
	((type *)(__mptr - offsetof(type, member))); })

エラーチェックなどを削除すると,container_of の実装は次のように簡略化できます

#define container_of(ptr, type, member) ((type *)( (char *)(ptr) - offsetof(type,member) ))

要は

  • offsetof()を使って構造体 type のメンバ変数 member のオフセットアドレスを計算
  • ptr からオフセットを引いて,構造体typeの先頭アドレスを計算
  • アドレスを type* 型にキャスト

しているだけです.

リストの操作:list_for_each_entry()

意味的には for_each_list と container_of を組み合わせたマクロです

#define list_for_each_entry(pos, head, member)				\
	for (pos = list_first_entry(head, typeof(*pos), member);	\
	     &pos->member != (head);					\
	     pos = list_next_entry(pos, member))

これをつかうと list_head 構造体を介さず,直接 struct Data のポインタでループを回せます.

  struct Data *p;
  list_for_each_entry(p, &head, list) {
      // 処理
  }

USB接続のSSD/HDDのSMART情報を見る方法

HDDやSSDの健康状態はS.M.A.R.T情報を調べると確認できます.ところが外付けドライブなどでは少し工夫しないとSMART情報が見えない場合もあります.

SMART情報が見えない理由

これは,PCとHDD/SSDとの接続に以下のような様々な組み合わせがあって

  • CPU → SATAコントローラ → HDD/SSD
  • CPU → USBコントローラ → 外付ドライブ( その中のSATAコントローラ → HDD/SSD)

その組み合わせによってSMART情報をやり取りする仕組みが異なっているためです.

よく使われる接続方式

ATA
SATAeSATAはこれに該当します
SCSI
最近はあまり見なくなりました.古いサーバ機だといまだにSCSIかもしれません.
nvme
NVM Express 方式です.つまり NVMe.今後の主流になると思います
sat
SCSI to ATA Translation の略です.USB接続の外付けHDDなどは大抵これになります.

smartctl コマンドのオプション

接続形式を明示的に指定するには,smartctl に -d オプションを付与します.

"-d" オプションのデフォルト値はautoです.例えば,/dev/sdaとして認識されているドライブのSMART情報を見る場合は,

$ sudo /usr/sbin/smartctl -a /dev/sda

を実行しますが,これは

$ sudo /usr/sbin/smartctl -d auto -a /dev/sda

を実行したことになります.

USB接続の外付けケースや,USB変換ケーブルを使っている場合は,とりあえず sat を指定するとうまくいくことが多いです.

$ sudo /usr/sbin/smartctl -d sat -a /dev/sda

pipで入れたパッケージを一括更新する

python のパッケージをコマンド一行で最新版にアップデートする方法です

コマンド

普段 sudo を使わずに,--user つけてpipを実行している場合

$ pip3 list --format freeze --outdated | while IFS='==' read pkg dummy; do  pip3 install --user --upgrade $pkg ; done

普段 sudo を使ってpipを実行している場合

$ pip3 list --format freeze --outdated | while IFS='==' read pkg dummy; do sudo  pip3 install --upgrade $pkg ; done

これだけです

注意

上記の例ではpip3を使っています.実際にコマンドを実行する際には,使っている環境に合わせて pip-3 とか pip とかに書き換えて使用してください

解説

pip コマンドと bash を組み合わせています

更新が必要なパッケージを pip3 コマンドで調べます

$  pip3 list --format freeze --outdated

例えば以下のような出力が得られます

Mako==1.0.7

これを bash の read コマンドで読み取ります

説明のために読み取った値を表示するだけのコードを書いておきます

$ pip3 list --format freeze --outdated | while   IFS='==' read pkg dummy; do  echo "pkg:[$pkg]   dummy:[$dummy]"; done

変数IFSを使って区切り文字として"=="を指定しています.この場合 read コマンドは,"Make==1.0.7"という入力行にたいして,最初のトークン"Mako"を変数pkgに,残りのトークン"1.0.7"を変数dummyに格納します

これで変数pkg にパッケージ名が入るので,あとは pip3 install --user --upgrade $pkg でパッケージを更新していきます