debugfs でカーネル内の変数をユーザ空間に見せる方法

LKMLに投げたパッチ を修正している最中に debugfs を使いたくなる場面があったんですが *1、 debugfs の作成は早すぎるとサイレントに失敗するっぽいので最低限動くコードを残しておきます。

TL; DR

こんな感じのをファイル先頭に書くと動きます *2 。 詳細なAPIとかは debugfs のAPIドキュメンテーション を参考にしてください。

#include <linux/debugfs.h>

u64 my_value = 0xbeef;
static __init int setup_my_debugfs(void) {
       static struct dentry *dir;
       static struct dentry *file;
       dir = debugfs_create_dir("my_dir", NULL);
       debugfs_create_x64("my_value", 0400, dir, &my_value);

       return 0;
}

postcore_initcall(setup_my_debugfs);

いつ呼ぶと失敗するか

横着して kernel/nscommon.c の __ns_common_init() で ns->inum が NET_NS_INIT_INO になるタイミング *3 で debugfs_create_dir() を呼び出したが、/sys/kernel/debug 以下に当該ディレクトリが作られなかった。

ちゃんと確認してないんですが、たぶん何も printk せずに ERR_PTR を返したんじゃないかなあと思ってます *4 。 ただ本当はカーネルモジュールも呼び出しうる関数 *5 なので、サイレントにこけるってあんまよくないんじゃないかなあと思ってます。気が向いたらパッチ投げるかも。

*1:普通はprintkやデバッガ、kprobeあたりで足りるんですが、今回はRCUが(たぶん)絡んでいて、syscall が呼ばれたタイミングで広範囲のリストの値をなるはやで取って debugfs_create_blob() とかでユーザ空間に見せたかったため

*2:少なくとも Linux 7.0 の debugfs/dma_pools はこんな感じで書かれていた

*3:つまり、root netns が初期化されるタイミング。ネットワークスタックの初期化時。

*4:debugfs_create_dir() は失敗時にNULLを返すと思い込んでいた…

*5:要検証

copilot.vim の認証情報を初期化する方法

Vim/Neovim 上で GitHub Copilot 利用する際には copilot.vim を使うことが多い。

Vim *1で copilot.vim をアンインストール後に再インストールしようとすると、古い認証情報が残っていて認証に失敗する場合があった。 copilot.vim の認証失敗時の処理が壊れているらしく、 :Copilot setup などで再設定しようとしてもこの問題は解決しない。 したがって、これを解決するには copilot.vim の認証情報を削除する必要がある。

認証情報を削除し、再度 Copilot の認証を行えるようにするためには、以下の方法が有効であった。 ただし、この方法で設定ファイルを削除すると、他のエディタの認証情報も消える場合があるため注意されたい。

  • ~/.config/github-copilot を削除する*2

なお、設定ファイルの全削除という乱暴な手順を踏むのは、あくまで copilot.vim の認証失敗時のバグを回避するためなので、 nvim での Copilot Language Server + copilot.lua 環境 など今回と異なる構成では、 他エディタの設定を破壊しない別の手順(たとえば :Copilot auth の再実行など)を先に試してもよいだろう。

*1:具体的な環境は macOS 15.3.1, Vim 9.1, copilot.vim commit 5015939f だが、おそらく他のバージョンでも同様の問題が起こる

*2:参考: https://github.com/orgs/community/discussions/152171

シェルスクリプトでの簡易的なロギング方法

シェルスクリプトの標準出力をstdout.logに、標準エラー出力をstderr.logに出力したくなるときがあります。 以下のようにシェルスクリプト実行時に標準出力をリダイレクトしてもいいのですが、これを毎回打ち込むのはやや面倒です。

./script.sh 1>stdout.log 2>stderr.log

この場合、以下のようにexecの出力をリダイレクトすることで、プロセスの標準出力・標準エラー出力を切り替えられます。execによる出力の切り替えは、少なくともbash, zsh, dashで正しく機能します。

#!/bin/sh

exec 1>stdout.log 2>stderr.sh

echo 'standard output'
echo 'standard error' 1>&2

補足

execはコマンドラインシェルの組み込みコマンドなので、シェルによっては異なる挙動をするかもしれません。 bashのmanページには「If command is not specified, any redirections take effect in the current shell, and the return status is 0. If there is a redirection error, the return status is 1.」という記載があったため、bashにおいては仕様通りの挙動のようです*1。

*1:POSIXでどうなっているかは未調査です

NamedTupleを継承したクラスの初期化でAttributeErrorが出る問題

以下のコードをPython3(Python 3.12.3)で実行すると、AttributeError: can't set attributeというエラーメッセージが現れる。

一見すると正しいコードだが、 @dataclassメタクラスで作成される__init__()とNamedTupleから継承される__init__()が競合している。 したがって、@dataclassの行を削除するとエラーが解消される。

from typing import NamedTuple
from dataclasses import dataclass

@dataclass  # この行が原因
class MyTuple(NamedTuple):
    x: int
    y: int

t = MyTuple(1, 2)

エラーメッセージの詳細

参考までに、実際に現れるエラーメッセージは以下の通り:

$ python3 namedtuple.py
Traceback (most recent call last):
  File "/path/to/namedtuple.py", line 9, in <module>
    t = MyTuple(1, 2)
        ^^^^^^^^^^^^^
  File "<string>", line 3, in __init__
AttributeError: can't set attribute

背景

データクラスを@dataclassからNamedTupleに移行する際に、うっかり@dataclassを削除し忘れていた。 どちらの__init__()もソースコードには現れないし、エラーメッセージも簡潔なので、原因究明にそこそこ時間をとられてしまった。 今後同様のバグを仕込んでしまった人(主に自分自身)のために記事として残しておく。

snap が参照するホームディレクトリを /home 以外に設定する方法

snapd は、ホームディレクトリが /home 以外に置かれている場合、ホームディレクトリへのアクセス権限の付与が正しく行えない。
この問題は、以下の手順でホームディレクトリの位置を明示的に指定すると解決する。

# snap のバージョンを確認する。バージョンが2.59以降なら snap refresh core の実行をスキップしてもよい
snap --version

# snap のバージョンを更新する
$ sudo snap refresh core

# ホームディレクトリを/local/homeに変更する。
# (/local/home はホームディレクトリに対応するパスに書き換える)
$ sudo snap set system homedirs=/local/home

参考ページ

snap set system homedirs=... でホームディレクトリを設定する機能は以下のPRで導入された。最近マージされたばかりなので、本稿執筆時点では日本語情報がほぼ皆無だった。 github.com

記事内容自体は以下を参考にした。以下のQ&Aの内容を補足すると、現行の Ubuntu 22.04 であれば、release/stable でもバージョン2.59以降の snapd が入る。また、22.04では snapd パッケージではなく core パッケージで snapd が管理されている(はず)。 askubuntu.com

この方法に関する公式ドキュメントは以下のページ。一応古い方法も紹介されているので、この記事で紹介した方法が機能しなかった場合は試してみるとよい。 https://snapcraft.io/docs/home-outside-home

Outlookでgit send-emailを使うための設定

数週間の格闘の末にOutlookでgit send-emailが使えるように設定できたので、設定内容について忘れないうちに記録しておく。 この記事の内容は2023年現在のOutlookのSMTPサーバーの挙動を前提にしていることに注意されたい。

なお、この方法で完璧にsend-emailが使えるというわけではない。 具体的には、複数のパッチを同時に送信した時にスレッドが崩れてしまうという問題が残っている。 これは、gitが設定したメールのMessage-IDがSMTPサーバーによって書き換えられ、 パッチのIn-Reply-Toに対応するMessage-IDが消滅してしまい、スレッドの親子関係の情報が失われてしまうためである。 この問題については依然調査中である1。

2026/01/06追記: git send-email には、--[no-]outlook-id-fix というオプションがある。これを使うと上記の問題はなんとかなるかも。

設定内容

outlook.jp ドメインのメールアドレスで git send-email を使うには、~/.gitconfig に以下の内容を追加するとよい。

なお、smtpuserの内容は実際のメールアドレスに、 smtpdomainの内容は送信側のローカルIPアドレス2に、smtppassの内容はOutlookのパスワード3に置き換える必要がある。 また、outlook.comなど.jp以外のドメインではsmtpserverの設定を変える必要があるかもしれない。

[sendemail]
  smtpserver = smtp.office365.com
  smtpuser = user@outlook.jp
  smtpencryption = tls
  smtpserverport = 587
  smtpdomain = 192.168.1.128
  smtppass = password

smtpdomainについて

Outlook以外でのgit send-emailの設定を紹介する多くの記事では、smtpdomainの設定を行っていない。 しかしながら、この設定を行わないと正常にsend-emailが行えない場合がある。 この節では、このようにsmtpdomainの設定を行う理由について説明する。

smtpdomainは、git send-emailがメールを送信する際にSMTPサーバーへ送るHELOコマンド4の内容として利用される。 このHELOコマンドの内容によっては、OutlookのSMTPサーバーにメールの送信を拒否されてしまうため、 smtpdomainの値をSMTPサーバーに拒否されないものに設定する必要があった。

今回試した限りでは、送信側マシンのローカルIPアドレスをHELOで送信するとうまく送信できたため、その方法を記している。 これはメールクライアントであるThunderbirdと同様の挙動のため、他のメールサービスでもとりあえず試す価値はあるかもしれない。

調査方法

(覚書)git send-email --smtp-debug=1などのオプションを利用して地道に調査を行った。だるかった…


  1. というか、書き換えられたMessage-IDを取得してIn-Reply-Toに反映するフックを書かないと解決しない気がする↩
  2. 試してないけど、ローカルIPのレンジなら何でも通るんじゃないかなぁ↩
  3. dotfilesにパスワードベタ書きが嫌な場合は、git-credentialを使うといいらしいです。試してませんが。↩
  4. see also: https://blog.smtps.jp/entry/2017/09/07/114820↩