GPTの文章がきらい

Claudeはそんなでもないんだけど、ChatGPT (OpenAI GPT)の作る文章がきらい。

分かりやすい例があったので (記事の内容とは関係ないけれど)、引用してコメントしていく。

了解。まずはメモから「暗黙の前提・制約・判断基準」を仮説化しつつ、設計の分岐点だけを突く質問を最大3つ投げます。あなたの回答に合わせて次の3問を出し、十分になったらトレードオフ表にまとめます。

では最初の3問です。どれも「方式選定(同期/キュー/バッチ)」の分岐を決める前提を炙り出すための質問です。

  1. 取りこぼし最小化の定義を、重複との関係で決めたいです。
    「1件も落とさない」優先なのか、「重複は絶対NG」なのか、どちらに寄せたいですか?(例:Pushは重複OKだがメールは重複NG、などチャネル別でも)

  2. 通知は主にどっちですか?(設計が大きく変わります)

    • 単一ユーザー起点のトランザクション通知(例:決済完了、コメント返信)
    • 多数ユーザーへの配信/キャンペーン型(例:お知らせ一斉送信、セグメント配信)
      ※後者が混ざるなら「1イベント→大量fan-out」をどこでやるかが重要になります。
  3. ユーザー体験として許容できる 遅延の上限再送の期限 はどれくらいですか?
    例:「基本は数秒〜数分、最悪でも30分以内」「翌日まで届けばよい」「24時間過ぎたら捨てる」など。
    (ここが決まると、同期/キュー/バッチの“勝ち筋”がほぼ決まります)

まずはこの3つに答えてください。返答をもとに、次の3問で「段階導入の切り方」「監視を最小化するための観測点」「失敗時の安全策(永続化/再試行/冪等)」に踏み込みます。

AI に指示するのではなく、まずは AI に質問させた方が捗る

了解。

全体がですます調なので、了解です、ね。短い方がいいなら、はい、でいいんじゃない?

設計の分岐点だけを突く質問を最大3つ投げます

  • だけ、最大: 不要な修飾しがち。
  • 分岐点、突く、投げる: 自分イケてるって酔ってる人みたいだからいやだ。もっと普通の言い方あるでしょう。

たとえばこう書いてほしい: 大まかな設計方針を決めるために、3つ質問をします。

では最初の3問です。どれも「方式選定(同期/キュー/バッチ)」の分岐を決める前提を炙り出すための質問です。

  • 炙り出す: 自分イケてるって(略)
  1. 通知は主にどっちですか?(設計が大きく変わります)
  • どっち: どちら
  1. ユーザー体験として許容できる 遅延の上限再送の期限 はどれくらいですか?
    例:「基本は数秒〜数分、最悪でも30分以内」「翌日まで届けばよい」「24時間過ぎたら捨てる」など。
    (ここが決まると、同期/キュー/バッチの“勝ち筋”がほぼ決まります)
  • 最悪: 最大
  • "勝ち筋": 自分イケてるって(略)

まずはこの3つに答えてください。返答をもとに、次の3問で「段階導入の切り方」「監視を最小化するための観測点」「失敗時の安全策(永続化/再試行/冪等)」に踏み込みます。

  • 切り方、観測点、踏み込む: 自分イケてるって(略)

Windows 11 Setup

プライベートも仕事もMacなんだけど、ひょんなことでプライベート用にWindowsラップトップが手に入ったので、手になじむようにセットアップする。

Windows 11 Pro (24H2)

Windows Settings

Remove browser tab from Alt-Tab menu

System > Multitasking > Show tabs from apps when snapping or pressing Alt+Tab > Don't show tabs

Desktop separation

System > Multitasking > Desktops > task bar > On all desktops System > Multitasking > Desktops > Alt+Tab > On all desktops Desktopまたいで移動したいことがよくあるので。

Key repeat

Control Panel > Keyboard > Repeat delay > shortest

Control Panel > Keyboard > Repeat rate> fastest

Default input method

画面ロックから復帰するとUIの言語のIMに変わるのが不便。UIの言語は英語にしておきたいが、入力はCorvusSKKを使いたい。

Time & Language > Typing > Advanced keyboard settings > Override for default input method > CorvusSKK (デフォルトは Use language list)

Keyboard

Dvorak

最初AutoHotKeyでやろうとしたがちょいちょいリマップが効かないことがあった。以下の設定で立ち上げて jk キーを連打していると hththtjhtjtht みたいにたまにjが混ざる。連打とかでなくて普通に文章を入力するときにも起こる。

#Requires AutoHotkey v2.0
j::h
k::t

解決できなかったので、おとなしく?レジストリで。英語はSettingsから United States-Dvorak を選ぶだけだが、日本語はレジストリから設定する必要がある。

Computer\HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Keyboard Layouts\00000411
Layout File -> KBDDV.dll

ただし、WSLgはQWERTYになってしまう。US-Dvorakの方はdvorakのままなのになー。

そのほかキーリマップ

レジストリでCapsLockをControlに置き換える。

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Keyboard Layout
Scancode Map -> hex:00,00,00,00,00,00,00,00,02,00,00,00,1d,00,3a,00,00,00,00,00

スペースはSandS (Space and Shift)に、Enterも同じようにControlとしても機能するようにする(Enter and Control?)。AutoHotKeyで設定する。

#Requires AutoHotkey v2.0

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Enter to Control while holding
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; https://www.autohotkey.com/boards/viewtopic.php?p=482892&sid=34ec48f6d1e7e9f8fce513d07c8737c6#p482892

*Enter::
{
    Send "{Blind}{RCtrl down}"
    KeyWait "Enter" ; waits for Enter to be released
}

*Enter Up::
{
    Send "{Blind}{RCtrl Up}"
    If (A_PriorKey = "Enter" AND A_TimeSincePriorHotkey < 300) {
        Send "{Blind}{Enter}"
    }
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; SandS
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
*Space::
{
    Send "{Blind}{Shift down}"
    KeyWait "Space" ; waits for Space to be released
}

*Space Up::
{
    Send "{Blind}{Shift Up}"
    If (A_PriorKey = "Space" AND A_TimeSincePriorHotkey < 300) {
        Send "{Blind}{Space}"
    }
}

参考にしたフォーラムのコードだと修飾キーとして動作してほしいときにもスペースやEnterが入力されてしまったのだが、 A_PriorHotKey のところを A_PriorKey にして調整すると、期待する動作になった。タイマーは、修飾キーのつもりで押したあと何もせずにやめたいときのため。Space/Enterを押して少し待ってからはなすと何も入力されない。

Emacsキーバインド。よく使う移動用のもののみ。TerminalとかEmacsなどは直接オリジナルの機能を使う。C-aとかC-fはWindowsアプリでもよく使うのでとりあえずC-M prefixで代替。ただよく間違えるので、AltキーをMacのCommand相当にしてしまってMacのふりをするのもありかもしれない。

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Emacs
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
excludedApps()
{
    if WinActive("ahk_class RAIL_WINDOW ahk_exe msrdc.exe")
        return 1
    if WinActive("ahk_class CASCADIA_HOSTING_WINDOW_CLASS ahk_exe WindowsTerminal.exe")
        return 1
    if WinActive("ahk_class Emacs ahk_exe emacs.exe")
        return 1
    return 0
}

#HotIf !excludedApps()
$^h::Send "{Backspace}"
$^d::Send "{Delete}"
$^a::Send "{Home}"
$^e::Send "{End}"
$^f::Send "{Right}"
$^b::Send "{Left}"
$^n::Send "{Down}"
$^p::Send "{Up}"
$^k::Send "+{End}^x"
$^y::Send "^v"

;; Evacuate freq used Ctrl+X to Alt+Ctrl+X
!^a::Send "^a"  ;; Select all
!^f::Send "^f"  ;; Find
!^n::Send "^n"  ;; New
#HotIf

SKK

CorvusSKKをインストールして、ACT の設定を入れる。CorvusSKKのGitHubリポジトリにACTのサンプルがある。選択、キー1、位置、仮名、あたりがACT(Dvorak)固有の設定になる。

Font

メイリオは長い文章が読みづらいので、Noto Sansに置き換える設定をWebブラウザに入れる。

Stylusプラグインで以下CSSをすべてのサイトに適用させる。

@font-face {
  font-family: Meiryo;
  src: local("Noto Sans JP");
}

@font-face {
  font-family: メイリオ;
  src: local("Noto Sans JP");
}

CSSメイリオを指定しているサイトも、指定していなくてデフォルトフォントに落ちる場合でも、Noto Sansで表示される。すばらしい。

WSL

開発環境の整備はこれから。

おまけ

前職場で使っていたWindowsはWin 7だったので、進歩を感じる。以前は以下のような設定、アプリを使っていた。

  • MacType
    • アンチエイリアス依存症なので。HiDPIディスプレイのおかげでフォントがきれいになって本当にうれしい。
  • QTTabBar
    • 仕事でたくさんフォルダを開く必要あったので。今回のマシンはプライベート用だけど、Explorerだけで仕事できるんじゃないかな。
  • Cygwin/Mintty
    • 手元でgrepとかawkとか使いたかったので。WSLでいい感じになることを期待している。シームレスに使える仮想マシンという理解。
    • Terminal Appはいいね。フォントやテーマを指定できて、背景を透明にできれば十分。しばらく使ってみる。

SpacesとかExposeみたいなのもあるし、だいぶ使いやすくなっていていい感じ。Macじゃなくてもいいかもなーというふうに感じた。

mDNSでサブドメインに応答したい

mDNSにサブドメインというものはなく、 abc.def.local の場合 "abc.def" がホスト名に相当する。しかし便宜上、この記事ではサブドメインと呼ぶ。

2020年にサブドメインをmDNS avahi でホストしようとしたときに記事にした。このときは、使いたいサブドメインごとに avahi-publish を実行するsystemdサービスを追加した。

naokton.hatenablog.com

2025年になりあらためて探してみたところ、あいかわらず avahi は対応していない (https://github.com/avahi/avahi/issues/40)。まあ mDNS の想定された使い方ではないからとか、そんなところなのだろうか。issueの中身は追っていない。

avahi は変わらなかったが go-avahi-cname といういいかんじのプログラムが公開されていたので、それを使ってみる。ホスト名が serv.local だったとすると、 *.serv.local にCNAMEを応答してくれる。サブドメインごとの対応が不要なので大変べんり。

github.com

インストール

バイナリをgithubのリリースページから落としてくる。

curl -LO curl -LO https://github.com/grishy/go-avahi-cname/releases/download/v2.1.1/go-avahi-cname_v2.1.1_linux_amd64.tar.gz
tar xf go-avahi-cname_v2.1.1_linux_amd64.tar.gz go-avahi-cname
sudo mv go-avahi-cname /usr/local/bin

サービス化

適当にsystemdのサービスファイルを作る

# /etc/systemd/system/go-avahi-cname.service
[Unit]
Description=Go-avahi-cname Service
After=avahi-daemon.service
Requires=avahi-daemon.service

[Service]
ExecStart=/usr/local/bin/go-avahi-cname subdomain
Restart=always
User=daemon
Group=daemon
Environment=PATH=/usr/local/bin:/usr/bin:/bin
WorkingDirectory=/usr/local/bin

[Install]
WantedBy=multi-user.target

enable &&start

sudo systemctl daemon-reload
sudo systemctl enable --now go-avahi-cname.service
systemctl status go-avahi-cname.service
● go-avahi-cname.service - Go-avahi-cname Service
     Loaded: loaded (/etc/systemd/system/go-avahi-cname.service; enabled; preset: enabled)
     Active: active (running) since Sat 2025-01-18 19:32:33 EST; 28min ago
   Main PID: 336826 (go-avahi-cname)
      Tasks: 8 (limit: 18797)
     Memory: 2.8M (peak: 3.4M)
        CPU: 167ms
     CGroup: /system.slice/go-avahi-cname.service
             └─336826 /usr/local/bin/go-avahi-cname subdomain

Jan 18 19:32:33 serv systemd[1]: Started go-avahi-cname.service - Go-avahi-cname Service.
Jan 18 19:32:33 serv go-avahi-cname[336826]: 19:32:33 INF creating publisher
Jan 18 19:32:33 serv go-avahi-cname[336826]: 19:32:33 INF getting FQDN from Avahi
Jan 18 19:32:33 serv go-avahi-cname[336826]: 19:32:33 INF running subdomain publisher fqdn=serv.local.
Jan 18 19:32:33 serv go-avahi-cname[336826]: 19:32:33 INF creating connection to multicast
Jan 18 19:32:33 serv go-avahi-cname[336826]: 19:32:33 INF start listening

確認

[20:07:31 nao@macmini] ~ % dns-sd -Q serv.local
DATE: ---Sat 18 Jan 2025---
20:08:04.057  ...STARTING...
Timestamp     A/R  Flags         IF  Name                          Type   Class  Rdata
20:08:05.552  Add  2             12  serv.local.                  Addr   IN     192.168.1.32
^C
[20:08:17 nao@macmini] ~ % dns-sd -Q cat.serv.local
DATE: ---Sat 18 Jan 2025---
20:08:19.776  ...STARTING...
Timestamp     A/R  Flags         IF  Name                          Type   Class  Rdata
20:08:20.070  Add  3             12  cat.serv.local.              CNAME  IN     serv.local.
20:08:20.070  Add  2             12  serv.local.                  Addr   IN     192.168.1.32
^C
[20:08:25 nao@macmini] ~ % dns-sd -Q dog.serv.local
DATE: ---Sat 18 Jan 2025---
20:08:49.552  ...STARTING...
Timestamp     A/R  Flags         IF  Name                          Type   Class  Rdata
20:08:49.999  Add  3             12  dog.serv.local.              CNAME  IN     serv.local.
20:08:49.999  Add  2             12  serv.local.                  Addr   IN     192.168.1.32
^C

Emacs 29.1 の What's New ピックアップ

Emacs 29.1のWhat'sの紹介記事を見て、自分的に気になったものを並べる。

split-root-window-below と split-root-window-right

ウィンドウ分割は C-x 2 で今のウィンドウを上下に分割、 C-x 3 で左右に分割なんだけど、例えばすでに左右に分割された状態で、その2つのウィンドウの下にウィンドウを入れようとしたときのコマンドがなかった。この例の場合についていえば、右のウィンドウを一度消してからc-x 2(上下分割), c-x 3(左右分割) とかなんとかしたりして、面倒だなと思っていた。必要になるのはたまにだけど。

そういうときに、今回追加された split-root-window-below (C-x w 2) とか split-root-window-right (C-x w 3) が使える。便利。

Emoji

emoji-list とか emoji-search とかで絵文字を挿入できるようになった。たぶん使わないけど、ためしておーとなった。

tree-sitter

使ってみたい。あとで時間あるときにためす。

pixel-scroll-precision-mode

普通のアプリケーションのように?なめらかなスクロールができるようになる。ためしてみたけどちょっとスクロール重くなったり固まったりして、そもそもマウスでスクロールする習慣ないからデフォルトで有効にはしないかな。

キーバインド、キーマップ設定関数

Use 'keymap-set' instead of 'define-key'.
Use 'keymap-global-set' instead of 'global-set-key'.
Use 'keymap-local-set' instead of 'local-set-key'.
Use 'keymap-global-unset' instead of 'global-unset-key'.
Use 'keymap-local-unset' instead of 'local-unset-key'.
Use 'keymap-substitute' instead of 'substitute-key-definition'.
Use 'keymap-set-after' instead of 'define-key-after'.
Use 'keymap-lookup' instead of 'lookup-key' and 'key-binding'.
Use 'keymap-local-lookup' instead of 'local-key-binding'.
Use 'keymap-global-lookup' instead of 'global-key-binding'.

define-keyglobal-set-key などに対応するあたらしい関数。今までの関数は使えなくなる訳ではないけれど、新しい方を推奨していくとのこと。参照記事にもあるけど、古い名前に馴染んでしまっているよなあ。まあleafとかuse-packageとか使っているとあまり気にする必要ないか。

Proced

proced-modeというのがあるのを今回知った。ためしてみてpsっぽい一覧が見えておーとなったけど、使わないかな..

Tramp

/docker:<コンテナ名> で動いているコンテナの中のファイルにアクセスできるようになった。コンテナ内のファイルをemacsでじっくり編集したくなることはあまりないけど、覚えておいてもいいかな。

Project

project-find-fileproject-kill-buffers など。project-find-file は Emacs 25のころからあったらしい。projectile を入れて projectile-find-file とか projectile-kill-buffers をよく使っているけど、それの代わりになるのかな。ivyとかその他パッケージがprojectile利用していそうなのでprojectileは消せないかな。それなら今のままでいいかな?

scratch-buffer

scratch buffer がなければ作るコマンド。これ欲しかった。たまにscratch buffer消しちゃったときに、やっぱり手頃なバッファーが欲しくなって scratchバッファーの作りかたを何度か検索した(そしてない事に驚いた)。Lisp評価できる必要ないし、適当な名前でバッファー作ればいいんだけども。initial-major-mode でscratch bufferのモード指定できたのか。fundamentalとかにしておくかな。

package-upgrade-all

大抵 list-package -> U -> x -> y と流れでやっているのを、ショートカットできる。何がアップデートされるのか分からないのはちょっと微妙。

M-SPC (cycle-spacing)

知らなかった。たくさんのスペースを1つにしたいときは多々あるので、覚えよう。記事にある M-^ (delete-indentation) も知らなかったけど、これも覚えよう。

restart-emacs

あるなら使うかもしれない。パッケージアップデートしたときとかゴミみたいなバッファーたくさんあって掃除したいときとか、再起動はたまにする。

duplicate-lineduplicate-dwim

C-a C-SPC C-n M-w C-yが染みついているのでいまさらという感じもあるが、よく使うものでもあるのでどこかに割り当てようかな。kill-ringが汚れないのもよい。

Global font size change (C-M + スクロールもしくは C-x C-M-=, C-x C-M--)

フォントサイズを変えたいときは大抵バッファーごとではなくてすべてのバッファーで変えたいのでうれしい。

ctrl + スクロール もしくは C-x C-=, C-x C-- でバッファー単位のフォントサイズ変更をしたときに、行番号とかtab-barのタブとかmode-lineとかの大きさが変わらなくて気持ち悪かったんだけど、こっちのフォントサイズ変更だと全部変わるのもうれしい。

Emacs is now capable of editing files with very long lines

たまにながーい行のあるファイルを扱うときにEmacsがおっっっそくなって心を無にすることがあるので、改善はうれしい。

Ubuntu 20.04 を Wireguard サーバー(ゲートウェイ)にする

Ubuntu 20.04をWireguard サーバーにし、さらにGWとして使えるように設定する。IPv6は設定しない。

OSの設定をする

ルーティングしてもらうために /etc/sysctl.conf の以下の行のコメントを外す。

net.ipv4.ip_forward=1

Wireguardをインストールする

$ sudo apt update
$ sudo apt install -y wiregaurd
$ sudo reboot

Wireguardを設定する

Wireguardの設定は、wg-quickでOS起動/停止時に有効化/無効化する方針で。

鍵ペアを作る (/etc/wireguard/{privatekey,publickey})。

$ sudo sh -c 'cd /etc/wireguard; wg genkey | tee privatekey | wg pubkey > publickey'
$ sudo chmod 600 /etc/wireguard/privatekey

wg-quick 用の設定ファイルを作成する。 xxx.conf の xxx がWireguardのインターフェース名として使われる。ここでは wg0 を使う。PrivateKeyには作成した privatekey の内容を転記する。IPアドレスやポート番号は適当に。PostUp/PreDownの第1引数 %i はWireguardのインターフェース名に置換される。第2引数は listen port、第3引数は wireguard用のセグメントを指定する。PeerにIPを手動で割り当てるのちょっとめんどい。

$ sudo vi /etc/wireguard/wg0.conf
[Interface]
SaveConfig = false
Address = 10.0.1.1/24
ListenPort = 47474
PrivateKey = xxxxxx
PostUp = /etc/wireguard/postup.sh %i 47474 10.0.1.0/24
PreDown = /etc/wireguard/predown.sh %i 47474 10.0.1.0/24

[Peer] # hogehoge PC
PublicKey = abcabc
AllowedIPs = 10.0.1.2/32

[Peer] # fugafuga PC
PublicKey = xyzxyz
AllowedIPs = 10.0.1.3/32

下はPeer側の設定例。PeerのPeerはサーバー。AllowedIPsを 0.0.0.0/0 としデフォルトゲートウェイをWireguardサーバーに向ける。ロケーションによってDNSレスポンスが変わる事もあるらしいので、Wireguardサーバーに近いDNSサーバーを使いたい場合は指定する。

[Interface]
PrivateKey = zzzzzz
Address = 10.0.1.2/32
DNS = xx.xx.xx.xx

[Peer]
PublicKey = abcabc
AllowedIPs = 0.0.0.0/0
Endpoint = xx.xx.xx.xx:47474

$ sudo chmod 600 /etc/wireguard/wg0.conf

wireguardを有効/無効にするときにfirewallの設定を追加/削除したいので、 postup.sh/predown.sh に書いておく。ポートを開く他に、FORWARDチェインの設定(ufw route allow ...) とIPマスカレードの設定(iptables -t nat ...)が必須。

[/etc/wireguard/postup.sh]
WG_IF=$1
PORT=$2
TUN_SEG=$3
GW_IF=$(ip -j route list default | python3 -c 'import sys,json; print(json.load(sys.stdin)[0]["dev"])')

ufw allow in on $GW_IF to any port $PORT proto udp
ufw allow in on $WG_IF
ufw route allow in on $WG_IF -s $TUN_SEG out on $GW_IF
iptables -t nat -I POSTROUTING -o $GW_IF -s $TUN_SEG -j MASQUERADE
ufw status verbose
[/etc/wireguard/predown.sh]
WG_IF=$1
PORT=$2
TUN_SEG=$3
GW_IF=$(ip -j route list default | python3 -c 'import sys,json; print(json.load(sys.stdin)[0]["dev"])')

ufw delete allow in on $GW_IF to any port $PORT proto udp
ufw delete allow in on $WG_IF
ufw route delete allow in on $WG_IF -s $TUN_SEG out on $GW_IF
iptables -t nat -D POSTROUTING -o $GW_IF -s $TUN_SEG -j MASQUERADE
ufw status verbose
$ sudo chmod 755 /etc/wireguard/post{up,down}.sh

Wireguardを有効化する

$ sudo systemctl enable --now wg-quick@wg0.service

Wireguard、他の状態を確認する

$ systemctl status wg-quick@wg0.service
$ journalctl -u wg-quick@wg0.service
$ sudo wg show wg0

ついでに他の設定も確認する

$ sudo ufw status verbose
$ sudo iptables -t nat -nvL
$ sudo sysctl -a | grep ip_forward

補足

上では混ぜて書いているが、Wireguardの設定の他に、Linuxルーター(GW)として動作させるためには、以下の設定を忘れないように。

kpartx -a xxx で追加したデバイスのソースイメージを探す

kpartx -a *.img でイメージファイルの各パーティションをアタッチした時に、デファイスファイルのどれがどれに対応しているか調べた。環境はUbuntu 20.04。

ためしに kpartx -a してみる。

### ディスクを作る
$ dd if=/dev/zero of=/tmp/test.img bs=1 count=0 seek=1G
### 適当にパーティションを作る
$ sgdisk -a1 -n1:34:2047 -t1:EF02 -n2:2048:+256M -t2:EF00 -n3:0:0:+100% -t3:8300 /tmp/test.img
$ sudo sgdisk -p /tmp/test.img
Disk /tmp/test.img: 2097152 sectors, 1024.0 MiB
Sector size (logical): 512 bytes
Disk identifier (GUID): BAC9F0D5-63A5-411B-9F48-E06CBB18E58B
Partition table holds up to 128 entries
Main partition table begins at sector 2 and ends at sector 33
First usable sector is 34, last usable sector is 2097118
Partitions will be aligned on 2-sector boundaries
Total free space is 0 sectors (0 bytes)

Number  Start (sector)    End (sector)  Size       Code  Name
   1              34            2047   1007.0 KiB  EF02
   2            2048          526335   256.0 MiB   EF00
   3          526336         2097118   767.0 MiB   8300

$ sudo kpartx -av /tmp/test.img
add map loop0p1 (253:0): 0 2014 linear 7:0 34
add map loop0p2 (253:1): 0 524288 linear 7:0 2048
add map loop0p3 (253:2): 0 1570783 linear 7:0 526336

$ ll /dev/mapper/
total 0
drwxr-xr-x  2 root root     120 Jul 22 17:28 ./
drwxr-xr-x 16 root root    4200 Jul 22 17:28 ../
crw-------  1 root root 10, 236 Apr 23 14:53 control
lrwxrwxrwx  1 root root       7 Jul 22 17:28 loop0p1 -> ../dm-0
lrwxrwxrwx  1 root root       7 Jul 22 17:28 loop0p2 -> ../dm-1
lrwxrwxrwx  1 root root       7 Jul 22 17:28 loop0p3 -> ../dm-2

名前からし/dev/mapper/loop0p{1..3} の親は /dev/loop0 であると推測できるが、一応確認してみる。

kpartx で接続された各パーティションは、device mapper によってマップされている。マッピングの様子は、たとえば dmsetup ls --tree で表示できる。

$ sudo dmsetup ls --tree
loop0p3 (253:2)
 └─ (7:0)
loop0p2 (253:1)
 └─ (7:0)
loop0p1 (253:0)
 └─ (7:0)

253:x や 7:0 はデバイスナンバーといい、major:minor の組で表わされる。 dev 下にあるデバイスファイルを ls -l で表示すると、オーナー/グループと日付の間に major, minor の形で表示される。たとえば dmsetup ls --tree の結果の loop0p3 を見ると、loop0p3 (dm-2) 自体は 253:2 で、親は 7:0 であるという。これは下の ls -l の結果と照合できる。

$ ls -l /dev/loop* /dev/mapper/* /dev/dm-*
brw-rw---- 1 root disk 253,   0 Jul 22 17:28 /dev/dm-0
brw-rw---- 1 root disk 253,   1 Jul 22 17:28 /dev/dm-1
brw-rw---- 1 root disk 253,   2 Jul 22 17:28 /dev/dm-2
crw-rw---- 1 root disk  10, 237 Jul 22 06:10 /dev/loop-control
brw-rw---- 1 root disk   7,   0 Jul 22 17:28 /dev/loop0
brw-rw---- 1 root disk   7,   1 Jul 22 07:57 /dev/loop1
brw-rw---- 1 root disk   7,  10 Jul 20 15:34 /dev/loop10
brw-rw---- 1 root disk   7,  11 Jul 21 06:45 /dev/loop11
brw-rw---- 1 root disk   7,   2 Jul 22 07:57 /dev/loop2
brw-rw---- 1 root disk   7,   3 Jul 22 07:56 /dev/loop3
brw-rw---- 1 root disk   7,   4 May 17 16:33 /dev/loop4
brw-rw---- 1 root disk   7,   5 Jun 21 20:28 /dev/loop5
brw-rw---- 1 root disk   7,   6 May  6 09:28 /dev/loop6
brw-rw---- 1 root disk   7,   7 Jun 14 15:28 /dev/loop7
brw-rw---- 1 root disk   7,   8 Jun 29 23:38 /dev/loop8
brw-rw---- 1 root disk   7,   9 Jul 22 07:57 /dev/loop9
crw------- 1 root root  10, 236 Apr 23 14:53 /dev/mapper/control
lrwxrwxrwx 1 root root        7 Jul 22 17:28 /dev/mapper/loop0p1 -> ../dm-0
lrwxrwxrwx 1 root root        7 Jul 22 17:28 /dev/mapper/loop0p2 -> ../dm-1
lrwxrwxrwx 1 root root        7 Jul 22 17:28 /dev/mapper/loop0p3 -> ../dm-2

ループデバイスにアタッチされているものは、losetup --list コマンドで確認できる。

$ losetup --list /dev/loop0
NAME       SIZELIMIT OFFSET AUTOCLEAR RO BACK-FILE     DIO LOG-SEC
/dev/loop0         0      0         0  0 /tmp/test.img   0     512

まとめると、今回の例では、 /tmp/test.img/dev/loop0 (7:0) にアタッチされており、その中のパーティション/dev/mapper/loop0p{1..3} にアタッチされている事がわかる。

kpartx -a でアタッチしたパーティションkpartx -d でデタッチできる

$ sudo kpartx -dv /tmp/test.img
del devmap : loop0p3
del devmap : loop0p2
del devmap : loop0p1
loop deleted : /dev/loop0

万が一、kpartx -a を何度もしたりアタッチしたファイルが何だったか忘れたなどの場合には、dmsetup, losetup を使って状況を確認、個別に削除できる。

補足

バイスのmajor番号は、/proc/device に一覧になっている。又、デバイス種ごとに番号は重複してもよいらしい。

$ egrep '^([A-C]| *[7] )' /proc/devices
Character devices:
  7 vcs
Block devices:
  7 loop

参考URL

vmdkをdockerコンテナ内でマウントする

Ubuntu cloud imageの初期状態のディスク内容を見たかったので、vmdkを直接マウントすることにした。ホストにいろいろインストールするのは避けたいので、dockerコンテナ内でやることにした。guestmount コマンドを使う。ホストはUbuntu 18.04。

  1. Ubuntu cloud-imageダウンロード

    $ wget https://cloud-images.ubuntu.com/focal/current/focal-server-cloudimg-amd64.ova
    $ tar tvf focal-server-cloudimg-amd64.ova
    -rw-r--r-- root/root     10760 2021-05-03 17:59 ubuntu-focal-20.04-cloudimg.ovf
    -rw-r--r-- root/root       213 2021-05-03 17:59 ubuntu-focal-20.04-cloudimg.mf
    -rw-r--r-- root/root 539142656 2021-05-03 17:59 ubuntu-focal-20.04-cloudimg.vmdk
    $ tar xf focal-server-cloudimg-amd64.ova ubuntu-focal-20.04-cloudimg.vmdk
    
  2. コンテナ起動
    fuseが必要になるので、/dev/fuseをマウントすると共に、必要な権限を追加する(cap-add, security-opt) (ref)

    $ docker run -it --rm -v $(pwd)/ubuntu-focal-20.04-cloudimg.vmdk:/tmp/ubuntu-focal-20.04-cloudimg.vmdk \
      --device /dev/fuse --cap-add SYS_ADMIN --security-opt apparmor:unconfined \
      ubuntu:20.04 bash
    
  3. パッケージインストール
    ref

    # apt update && \
      DEBIAN_FRONTEND=noninteractive apt install \
        --no-install-recommends -y \
        libguestfs-tools qemu-utils linux-image-generic
    
  4. マウント
    guestmount は少し時間かかる。

    # guestmount -a /tmp/ubuntu-focal-20.04-cloudimg.vmdk -i --ro /mnt
    # ll /mnt
    total 88
    drwxr-xr-x 19 root root  4096 May  3 21:45 ./
    drwxr-xr-x  1 root root  4096 Jun 17 15:32 ../
    lrwxrwxrwx  1 root root     7 May  3 21:39 bin -> usr/bin/
    drwxr-xr-x  4 root root  4096 May  3 21:45 boot/
    drwxr-xr-x  5 root root  4096 May  3 21:42 dev/
    drwxr-xr-x 91 root root  4096 May  3 21:45 etc/
    drwxr-xr-x  2 root root  4096 Apr 15  2020 home/
    lrwxrwxrwx  1 root root     7 May  3 21:39 lib -> usr/lib/
    lrwxrwxrwx  1 root root     9 May  3 21:39 lib32 -> usr/lib32/
    lrwxrwxrwx  1 root root     9 May  3 21:39 lib64 -> usr/lib64/
    lrwxrwxrwx  1 root root    10 May  3 21:39 libx32 -> usr/libx32/
    drwx------  2 root root 16384 May  3 21:45 lost+found/
    drwxr-xr-x  2 root root  4096 May  3 21:39 media/
    drwxr-xr-x  2 root root  4096 May  3 21:39 mnt/
    drwxr-xr-x  2 root root  4096 May  3 21:39 opt/
    drwxr-xr-x  2 root root  4096 Apr 15  2020 proc/
    drwx------  2 root root  4096 May  3 21:42 root/
    drwxr-xr-x  3 root root  4096 May  3 21:42 run/
    lrwxrwxrwx  1 root root     8 May  3 21:39 sbin -> usr/sbin/
    drwxr-xr-x  6 root root  4096 May  3 21:42 snap/
    drwxr-xr-x  2 root root  4096 May  3 21:39 srv/
    drwxr-xr-x  2 root root  4096 Apr 15  2020 sys/
    drwxrwxrwt  2 root root  4096 May  3 21:42 tmp/
    drwxr-xr-x 15 root root  4096 May  3 21:41 usr/
    drwxr-xr-x 13 root root  4096 May  3 21:42 var/
    

見れた!