Skip to main content

Redox OS を動かしてみる

· 12 min read

Redox OS Image source: https://gitlab.redox-os.org/redox-os/backgrounds/-/tree/master

概要

Redox OS は Rust で書かれた Unix ライクなオペレーティングシステムです。

カーネルのアーキテクチャがマイクロカーネルであることや Scheme 、Everything is URL など、設計にも尖った特徴があります。

今回は Building Redox を参考に Redox OS をビルドして QEMU 上で動かしてみます。

(実機で動かそうとしたら失敗した…)

Prebuild image で動かす

下記に Redox OS の image が置かれているので redox_demo.* のイメージを使って動かしてみる。

https://static.redox-os.org/img/x86_64/

先に結果を言うと、下記のドキュメントにあるように qemu で動かすのが一番簡単だった。

https://doc.redox-os.org/book/ch02-01-running-vm.html

virt-manager + liveiso (or harddrive)

liveiso と harddrive でバージョンが違う場合があるようなので動かない場合は別の方法を試したほうが良さそう。

自分が試したときは harddrive のほうがバージョンが新しく、COSMIC デスクトップからポートされたエディタやファイルマネージャが使えるようになっていた。

起動したときの画面はこんな感じ。

解像度を設定する画面

ログイン画面。デフォルトユーザの情報は下記にある。

https://doc.redox-os.org/book/ch02-01-running-vm.html#using-the-emulation

デスクトップ画面。使用感は結構もっさりしていて、UI のふるさもある。

orbital というデスクトップ環境が使われていて、Terminal が透過しているが、おそらくそのせいでターミナルウィンドウを動かすと遅い。

また、自分がネットワーク周りの設定を理解していないからかもしれないが、ブラウザでネットワークに接続できなかった。

  • メリット
    • gui で比較的簡単に操作できる
  • デメリット
    • ネットワークの設定が少し複雑?
    • デスクトップ環境が重い

qemu + harddrive

下記のドキュメントを参考に qemu で動かす。

https://doc.redox-os.org/book/ch02-01-running-vm.html

image のパスだけ修正した下記コマンドを実行。

SDL_VIDEO_X11_DGAMOUSE=0 qemu-system-x86_64 -d cpu_reset,guest_errors -smp 4 -m 2048 \
-chardev stdio,id=debug,signal=off,mux=on,"" -serial chardev:debug -mon chardev=debug \
-machine q35 -device ich9-intel-hda -device hda-duplex -netdev user,id=net0 \
-device e1000,netdev=net0 -device nec-usb-xhci,id=xhci -enable-kvm -cpu host \
-drive file=`echo $HOME/os_imgs/redox_demo_x86_64_2024-05-01_993_harddrive.img`,format=raw

実行するとこんな出力が出る。

最後に VNC server running on ::1:5900 と出たので remmina で接続したところ virt-manager と同じく解像度の設定画面 -> ログイン画面 -> デスクトップ画面の流れで動かせた。

解像度設定画面

ログイン画面

デスクトップ画面

virt-manager と比べて動作は軽く、ネットワークも問題なく接続できた。

virt-manager が悪いとかではなくバージョンが違うせいかもしれない。(liveiso => v0.3.4, harddrive => v0.4.1)

  • メリット
    • ドキュメントに書いてあるとおりにやれば基本的に動く
    • ネットワークの設定をコマンドオプションで指定しただけでつながる
    • (デスクトップ環境が軽い)
  • デメリット
    • GUI がある desktop のイメージを動かすときに VNC client が必要 (server なら関係ない)

build して動かす

Redox のビルドシステム・ディレクトリ構成

Redox OS は redox-os/redox がメインのリポジトリでその下に submodule として filesystem のリポジトリや build システムの cookbook がある。

redox
├── bootstrap.sh* <-- ビルド環境を構築するスクリプト
├── build/ <-- build して生成された image ファイルなどが置かれる
├── build.sh* <-- イメージのビルドに使えるスクリプト(直接 make でビルドしたので今回は使わなかった)
├── config/ <-- ビルド時に含めるパッケージ情報がある
├── CONTRIBUTING.md
├── cookbook/ <-- kernel やアプリケーションのビルドスクリプトなどが submodule として置かれている
├── docker/
├── HARDWARE.md
├── installer/ <-- make でビルドするときに config を読んで package のリストを取得するために使われている
├── LICENSE
├── Makefile
├── mk/ <-- Makefile で include されるファイル
├── podman/
├── podman_bootstrap.sh*
├── prefix/
├── README.md
├── redoxfs/ <-- filesystem のリポジトリ
├── relibc/ <-- redox の libc
├── rust/
├── rust-toolchain.toml
└── scripts/

cookbook の中に recipe.toml があり、カーネルやアプリケーションのビルド方法が書かれている。

redox-os/cookbook

ビルド

ビルドは下記のように実行する。

make all CONFIG_NAME=<config>

# image が生成されて qemu で実行される
make qemu CONFIG_NAME=<config>

CONFIG_NAME は config/ にある toml ファイルの名前を指定する。(e.g. server.toml => server)

config/
├── aarch64/
│ └── ...
├── acid.toml
├── base.toml
├── desktop-minimal.toml
├── desktop.toml
├── dev.toml
├── i686/
│ └── ...
├── net.toml
├── redoxer-gui.toml
├── redoxer.toml
├── resist.toml
├── server-minimal.toml
├── server.toml
└── x86_64/
├── <config>.toml
├── acid.toml
├── ci.toml
├── demo.toml
├── desktop-contain.toml
├── desktop-minimal.toml
├── desktop.toml
├── dev.toml
├── jeremy.toml
├── resist.toml
├── server-minimal.toml
└── server.toml

Building Redox

試しに systemcall を追加してみる

"hello world" を出力するだけの system call を追加してみる。

ref. https://gitlab.redox-os.org/Forest0923/redox/-/tree/add-helloworld-syscall?ref_type=heads

一度 make all でビルドした場合、redox リポジトリ下の cookbook/recipes/core/kernel/source/syscall に kernel のソースコードがダウンロードされるので下記の caller, handler を追加する。

また、追加したシステムコールを呼び出すアプリケーションを boot した redox 上でコンパイルして実行する方法がぱっと見てわからなかったので、ビルド段階でテスト用のアプリケーションを追加することにした。

caller (library)

アプリケーション側から呼び出すためのライブラリが redox-os/syscall にある。

src/caller.rs に system call の呼び出しを行う関数が定義されているのでここに helloworld() を追加。

/// Hello world
pub fn helloworld() -> Result<usize> {
unsafe { syscall0(SYS_HELLOWORLD) }
}

src/number.rs に system call の番号を定義する。

pub const SYS_HELLOWORLD: usize = 999;

ちなみに syscall0 の関数は x86_64 向けの場合はマクロで下記のように定義されている。

ref. https://gitlab.redox-os.org/Forest0923/syscall/-/blob/add-helloworld-syscall/src/arch/x86_64.rs?ref_type=heads#L9-49

macro_rules! syscall {
($($name:ident($a:ident, $($b:ident, $($c:ident, $($d:ident, $($e:ident, $($f:ident, )?)?)?)?)?);)+) => {
$(
pub unsafe fn $name(mut $a: usize, $($b: usize, $($c: usize, $($d: usize, $($e: usize, $($f: usize)?)?)?)?)?) -> Result<usize> {
asm!(
"syscall",
inout("rax") $a,
$(
in("rdi") $b,
$(
in("rsi") $c,
$(
in("rdx") $d,
$(
in("r10") $e,
$(
in("r8") $f,
)?
)?
)?
)?
)?
out("rcx") _,
out("r11") _,
options(nostack),
);

Error::demux($a)
}
)+
};
}

syscall! {
syscall0(a,);
syscall1(a, b,);
syscall2(a, b, c,);
syscall3(a, b, c, d,);
syscall4(a, b, c, d, e,);
syscall5(a, b, c, d, e, f,);
}

syscall handler

kernel 側では syscall 命令が実行されたとき用にハンドラの実装が必要で redox-os/kernel にカーネル側のコードがある。

x86_64 の場合、MSR_LSTAR にシステムコールハンドラのアドレスを設定する。 その処理を行っているのは src/arch/x86_64/interrupt/syscall.rs で下記のように wrmsr で syscall_instruction を登録している。

pub unsafe fn init() {
// IA32_STAR[31:0] are reserved.

// The base selector of the two consecutive segments for kernel code and the immediately
// suceeding stack (data).
let syscall_cs_ss_base = (gdt::GDT_KERNEL_CODE as u16) << 3;
// The base selector of the three consecutive segments (of which two are used) for user code
// and user data. It points to a 32-bit code segment, which must be followed by a data segment
// (stack), and a 64-bit code segment.
let sysret_cs_ss_base = ((gdt::GDT_USER_CODE32_UNUSED as u16) << 3) | 3;
let star_high = u32::from(syscall_cs_ss_base) | (u32::from(sysret_cs_ss_base) << 16);

msr::wrmsr(msr::IA32_STAR, u64::from(star_high) << 32);
msr::wrmsr(msr::IA32_LSTAR, syscall_instruction as u64);
...

syscall_instruction は linux の entry_SYSCALL_64 で見たことがありそうな感じ。

#[no_mangle]
pub unsafe extern "C" fn __inner_syscall_instruction(stack: *mut InterruptStack) {
let allowed = ptrace::breakpoint_callback(PTRACE_STOP_PRE_SYSCALL, None)
.and_then(|_| ptrace::next_breakpoint().map(|f| !f.contains(PTRACE_FLAG_IGNORE)));

if allowed.unwrap_or(true) {
let scratch = &(*stack).scratch;

syscall::syscall(
scratch.rax,
scratch.rdi,
scratch.rsi,
scratch.rdx,
scratch.r10,
scratch.r8,
&mut *stack,
);
}

ptrace::breakpoint_callback(PTRACE_STOP_POST_SYSCALL, None);
}

#[naked]
#[allow(named_asm_labels)]
pub unsafe extern "C" fn syscall_instruction() {
core::arch::asm!(concat!(
// Yes, this is magic. No, you don't need to understand
"swapgs;", // Swap KGSBASE with GSBASE, allowing fast TSS access.
"mov gs:[{sp}], rsp;", // Save userspace stack pointer
"mov rsp, gs:[{ksp}];", // Load kernel stack pointer
"push QWORD PTR {ss_sel};", // Push fake userspace SS (resembling iret frame)
"push QWORD PTR gs:[{sp}];", // Push userspace rsp
"push r11;", // Push rflags
"push QWORD PTR {cs_sel};", // Push fake CS (resembling iret stack frame)
"push rcx;", // Push userspace return pointer

// Push context registers
"push rax;",
push_scratch!(),
push_preserved!(),

// TODO: Map PTI
// $crate::arch::x86_64::pti::map();

// Call inner funtion
"mov rdi, rsp;",
"call __inner_syscall_instruction;",
...

__inner_syscall_instructionsyscall::syscall は下記のように実装されている。syscall::syscall()

/// This function is the syscall handler of the kernel, it is composed of an inner function that returns a `Result<usize>`. After the inner function runs, the syscall
/// function calls [`Error::mux`] on it.
pub fn syscall(
a: usize,
b: usize,
c: usize,
d: usize,
e: usize,
f: usize,
stack: &mut InterruptStack,
) {
#[inline(always)]
fn inner(a: usize, b: usize, c: usize, d: usize, e: usize, f: usize) -> Result<usize> {
//SYS_* is declared in kernel/syscall/src/number.rs
match a & SYS_CLASS {
SYS_CLASS_FILE => {
let fd = FileHandle::from(b);
match a & SYS_ARG {
SYS_ARG_SLICE => match a {
SYS_WRITE => file_op_generic(fd, |scheme, number| {
scheme.kwrite(number, UserSlice::ro(c, d)?)
}),
...
_ => return Err(Error::new(ENOSYS)),
},
SYS_ARG_MSLICE => match a {
SYS_READ => file_op_generic(fd, |scheme, number| {
scheme.kread(number, UserSlice::wo(c, d)?)
}),
...
_ => return Err(Error::new(ENOSYS)),
},
_ => match a {
...
SYS_CLOSE => close(fd).map(|()| 0),
_ => return Err(Error::new(ENOSYS)),
},
}
}
SYS_CLASS_PATH => match a {
SYS_OPEN => open(UserSlice::ro(b, c)?, d).map(FileHandle::into),
...
_ => Err(Error::new(ENOSYS)),
},
_ => match a {
...
SYS_HELLOWORLD => helloworld().map(|()| 0),

_ => Err(Error::new(ENOSYS)),
},
}
}
...
}

SYS_HELLOWORLD のときの実際の処理は helloworld() で下記のように定義

ref. https://gitlab.redox-os.org/Forest0923/kernel/-/blob/4c750d4acb233777e12ded8a4a19dff99fb28f0b/src/syscall/hello.rs

use crate::syscall::error::*;
pub fn helloworld() -> Result<()> {
println!("SYS_HELLOWORLD: Hello, world!");
Ok(())
}

sample application

sys_helloworld を呼び出すアプリケーションを追加する。

https://gitlab.redox-os.org/Forest0923/helloworld

recipe の書き方は下記のようになっている。

https://doc.redox-os.org/book/ch09-01-including-programs.html#setting-up-the-recipe

[source]
git = "<repository url>"
branch = "<branch name>"

[build]
template = "custom" # custom, cargo, ...
script = """
echo "build script"
"""

redox-os/redox で config/x86_64/forest0923.toml に

include = ["../desktop.toml"]

[packages]
helloworld = {}
orbterm = {}

実行したところ QEMU のログに SYS_HELLOWORLD: Hello, world! と出力され、追加したシステムコールを呼び出すことができた。

% make qemu CONFIG_NAME=forest0923
SDL_VIDEO_X11_DGAMOUSE=0 qemu-system-x86_64 -d guest_errors -name "Redox OS x86_64" -device nec-usb-xhci,id=xhci -smp 4 -m 2048 -chardev stdio,id=debug,signal=off,mux=on,"" -serial chardev:debug -mon chardev=debug -machine q35 -device ich9-intel-hda -device hda-output -netdev user,id=net0 -device e1000,netdev=net0 -object filter-dump,id=f1,netdev=net0,file=build/x86_64/forest0923/network.pcap -enable-kvm -cpu host \
-drive file=build/x86_64/forest0923/harddrive.img,format=raw \
-drive file=build/x86_64/forest0923/extra.img,format=raw
VNC server running on ::1:5900

...

########## Redox OS ##########
# Login with the following: #
# `user` #
# `root`:`password` #
##############################

redox login: DHCP: Offer IP: [10, 0, 2, 15], Server IP: [10, 0, 2, 2]
DHCP: Message Type: [2]
DHCP: Server ID: [10, 0, 2, 2]
DHCP: Subnet Mask: [255, 255, 255, 0]
DHCP: Router: [10, 0, 2, 2]
DHCP: Domain Name Server: [10, 0, 2, 3]
DHCP: Lease Time: [0, 1, 81, 128]
DHCP: New IP: 10.0.2.15/24
DHCP: New Router: default via 10.0.2.2 dev eth0 src 10.0.2.15
127.0.0.0/8 dev loopback src 127.0.0.1
10.0.2.0/24 dev eth0 src 10.0.2.15
DHCP: New DNS: 10.0.2.3
DHCP: Sent Request
DHCP: Ack IP: [10, 0, 2, 15], Server IP: [10, 0, 2, 2]
SYS_HELLOWORLD: Hello, world!

おわり

Redox OS はまだ開発段階で不足している機能が多いですが、コンセプトも面白いし、継続的に開発もされているので引き続き注目していきたい。

おまけ

Linux などと比べたときの機能の違い

Rust で書かれた OS たち

Container の互換性を調べていたら色々と脱線した話

· 14 min read

概要

Docker などのコンテナ技術では、ホスト OS のカーネルを共有してコンテナイメージにディストリビューションやアプリケーションの依存関係を含めることで、異なる実行環境を再現しています。

このとき、ホスト OS とコンテナイメージのディストリビューションが異なるとカーネルの違いによって原理的には互換性の問題が発生するはずです。 今回は実際に古いカーネルを使用しているホスト OS 上で比較的新しいカーネルを使用しているディストリビューションを使用してアプリケーションを実行してどのような問題が起こるのか試してみました。

実行環境

  • env1

    • Host OS:
      • [mmori@localhost ~]$ cat /etc/redhat-release
        CentOS Linux release 7.9.2009 (Core)
        [mmori@localhost ~]$ uname -a
        Linux localhost.localdomain 3.10.0-1160.71.1.el7.x86_64 #1 SMP Tue Jun 28 15:37:28 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux
    • Container:
      • [mmori@localhost ~]$ sudo docker image ls
        REPOSITORY TAG IMAGE ID CREATED SIZE
        rockylinux 9.3 b72d2d915008 5 months ago 176MB
  • env2

    • Host OS:
      • [mmori@localhost ~]$ cat /etc/redhat-release
        Rocky Linux release 9.3 (Blue Onyx)
        [mmori@localhost ~]$ uname -a
        Linux localhost.localdomain 5.14.0-362.8.1.el9_3.x86_64 #1 SMP PREEMPT_DYNAMIC Wed Nov 8 17:36:32 UTC 2023 x86_64 x86_64 x86_64 GNU/Linux
    • Container:
      • [mmori@localhost ~]$ sudo podman image ls
        REPOSITORY TAG IMAGE ID CREATED SIZE
        quay.io/centos/centos centos7.9.2009 8652b9f0cb4c 3 years ago 212 MB

方針

カーネルのバージョンを確認して実装されているシステムコールの差分から互換性の問題を調査する。

Red Hat Enterprise Linux のリリース日 を見ると、RHEL 7 と RHEL 9 のカーネルバージョンは以下の通り。

リリースカーネルバージョン
RHEL 7.93.10.0-1160
RHEL 9.35.14.0-362.8.1.el9_3

Linux v3.10.0 と v5.14.0 のシステムコールのテーブルを確認する。

...
309 common getcpu sys_getcpu
310 64 process_vm_readv sys_process_vm_readv
311 64 process_vm_writev sys_process_vm_writev
312 common kcmp sys_kcmp
313 common finit_module sys_finit_module

#
# x32-specific system call numbers start at 512 to avoid cache impact
# for native 64-bit operation.
#
512 x32 rt_sigaction compat_sys_rt_sigaction
513 x32 rt_sigreturn stub_x32_rt_sigreturn
514 x32 ioctl compat_sys_ioctl
515 x32 readv compat_sys_readv
516 x32 writev compat_sys_writev
...
...
309 common getcpu sys_getcpu
310 64 process_vm_readv sys_process_vm_readv
311 64 process_vm_writev sys_process_vm_writev
312 common kcmp sys_kcmp
313 common finit_module sys_finit_module
314 common sched_setattr sys_sched_setattr
315 common sched_getattr sys_sched_getattr
316 common renameat2 sys_renameat2
317 common seccomp sys_seccomp
318 common getrandom sys_getrandom
319 common memfd_create sys_memfd_create
320 common kexec_file_load sys_kexec_file_load
321 common bpf sys_bpf
322 64 execveat sys_execveat
323 common userfaultfd sys_userfaultfd
324 common membarrier sys_membarrier
325 common mlock2 sys_mlock2
326 common copy_file_range sys_copy_file_range
327 64 preadv2 sys_preadv2
328 64 pwritev2 sys_pwritev2
329 common pkey_mprotect sys_pkey_mprotect
330 common pkey_alloc sys_pkey_alloc
331 common pkey_free sys_pkey_free
332 common statx sys_statx
333 common io_pgetevents sys_io_pgetevents
334 common rseq sys_rseq
# don't use numbers 387 through 423, add new calls after the last
# 'common' entry
424 common pidfd_send_signal sys_pidfd_send_signal
425 common io_uring_setup sys_io_uring_setup
426 common io_uring_enter sys_io_uring_enter
427 common io_uring_register sys_io_uring_register
428 common open_tree sys_open_tree
429 common move_mount sys_move_mount
430 common fsopen sys_fsopen
431 common fsconfig sys_fsconfig
432 common fsmount sys_fsmount
433 common fspick sys_fspick
434 common pidfd_open sys_pidfd_open
435 common clone3 sys_clone3
436 common close_range sys_close_range
437 common openat2 sys_openat2
438 common pidfd_getfd sys_pidfd_getfd
439 common faccessat2 sys_faccessat2
440 common process_madvise sys_process_madvise
441 common epoll_pwait2 sys_epoll_pwait2
442 common mount_setattr sys_mount_setattr
443 common quotactl_fd sys_quotactl_fd
444 common landlock_create_ruleset sys_landlock_create_ruleset
445 common landlock_add_rule sys_landlock_add_rule
446 common landlock_restrict_self sys_landlock_restrict_self
447 common memfd_secret sys_memfd_secret

#
# Due to a historical design error, certain syscalls are numbered differently
# in x32 as compared to native x86_64. These syscalls have numbers 512-547.
# Do not add new syscalls to this range. Numbers 548 and above are available
# for non-x32 use.
#
512 x32 rt_sigaction compat_sys_rt_sigaction
513 x32 rt_sigreturn compat_sys_x32_rt_sigreturn
514 x32 ioctl compat_sys_ioctl
515 x32 readv sys_readv
516 x32 writev sys_writev
...

簡単に使えそうなシステムコールとして 318 番の getrandom があるのでこれを使ってみる。

調査、テストコード

getrandom

SYSCALL_DEFINE3(getrandom, char __user *, ubuf, size_t, len, unsigned int, flags)
{
struct iov_iter iter;
int ret;

if (flags & ~(GRND_NONBLOCK | GRND_RANDOM | GRND_INSECURE))
return -EINVAL;

/*
* Requesting insecure and blocking randomness at the same time makes
* no sense.
*/
if ((flags & (GRND_INSECURE | GRND_RANDOM)) == (GRND_INSECURE | GRND_RANDOM))
return -EINVAL;

if (!crng_ready() && !(flags & GRND_INSECURE)) {
if (flags & GRND_NONBLOCK)
return -EAGAIN;
ret = wait_for_random_bytes();
if (unlikely(ret))
return ret;
}

ret = import_ubuf(ITER_DEST, ubuf, len, &iter);
if (unlikely(ret))
return ret;
return get_random_bytes_user(&iter);
}
  • test code
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/syscall.h>

#define BUF_SIZE 16

void print_buf(char *buf, int size) {
printf("buf = ");
for (int i = 0; i < size; i++) {
printf("%02x,", (unsigned char)buf[i]);
}
printf("\n");
}

int main() {
int ret;
char *buf = (char *) malloc(sizeof(char) * BUF_SIZE);
memset(buf, 0, BUF_SIZE);
print_buf(buf, BUF_SIZE);
ret = syscall(SYS_getrandom, buf, BUF_SIZE, 0);
printf("ret = %d\n", ret);
print_buf(buf, BUF_SIZE);
}

pidfd_open

最初は getrandom だけで試すつもりだったが、実際に実行すると centos7 のカーネルでも getrandom が使えてしまった。 (RHEL 7 のベースとなるバージョンは Linux v3.10.0 で getrandom は実装されていないはずだが、3.10.0-1160.71.1.el7.x86_64 では追加のパッチがあたっているのだろう。)

追加で pidfd_open を試してみる。

存在する pid を引数にして実行すると process を参照する file descriptor を取得できる。 man page の use case を見ると使いみちが色々とある。

   Use cases for PID file descriptors
A PID file descriptor returned by pidfd_open() (or by clone(2)
with the CLONE_PID flag) can be used for the following purposes:

• The pidfd_send_signal(2) system call can be used to send a
signal to the process referred to by a PID file descriptor.

• A PID file descriptor can be monitored using poll(2),
select(2), and epoll(7). When the process that it refers to
terminates, these interfaces indicate the file descriptor as
readable. Note, however, that in the current implementation,
nothing can be read from the file descriptor (read(2) on the
file descriptor fails with the error EINVAL).

• If the PID file descriptor refers to a child of the calling
process, then it can be waited on using waitid(2).

• The pidfd_getfd(2) system call can be used to obtain a
duplicate of a file descriptor of another process referred to
by a PID file descriptor.

• A PID file descriptor can be used as the argument of setns(2)
in order to move into one or more of the same namespaces as
the process referred to by the file descriptor.

• A PID file descriptor can be used as the argument of
process_madvise(2) in order to provide advice on the memory
usage patterns of the process referred to by the file
descriptor.

process の監視などに使えそう。 調べてみると python の新しい機能として pidfd が使われるようになっているらしい。

実装や man page については以下の通り。

/**
* sys_pidfd_open() - Open new pid file descriptor.
*
* @pid: pid for which to retrieve a pidfd
* @flags: flags to pass
*
* This creates a new pid file descriptor with the O_CLOEXEC flag set for
* the task identified by @pid. Without PIDFD_THREAD flag the target task
* must be a thread-group leader.
*
* Return: On success, a cloexec pidfd is returned.
* On error, a negative errno number will be returned.
*/
SYSCALL_DEFINE2(pidfd_open, pid_t, pid, unsigned int, flags)
{
int fd;
struct pid *p;

if (flags & ~(PIDFD_NONBLOCK | PIDFD_THREAD))
return -EINVAL;

if (pid <= 0)
return -EINVAL;

p = find_get_pid(pid);
if (!p)
return -ESRCH;

fd = pidfd_create(p, flags);

put_pid(p);
return fd;
}
  • test code (ただ呼び出すだけのコード)
#include <stdio.h>
#include <stdlib.h>
#include <sys/syscall.h>
#include <unistd.h>

int main(int argc, char *argv[]) {
int pid = getpid();
if (argc != 1) {
pid = atoi(argv[1]);
}
printf("pid: %d\n", pid);
int pidfd = syscall(SYS_pidfd_open, pid, 0);
if (pidfd < 0) {
perror("pidfd_open");
return 1;
}
printf("pidfd: %d\n", pidfd);
sleep(1000);
close(pidfd);
return 0;
}
  • test code (man page のコード)
    • 監視対象のプロセスが終了すると poll で検知できる
#define _GNU_SOURCE
#include <poll.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/syscall.h>
#include <unistd.h>

static int
pidfd_open(pid_t pid, unsigned int flags)
{
return syscall(SYS_pidfd_open, pid, flags);
}

int
main(int argc, char *argv[])
{
int pidfd, ready;
struct pollfd pollfd;

if (argc != 2) {
fprintf(stderr, "Usage: %s <pid>\n", argv[0]);
exit(EXIT_SUCCESS);
}

pidfd = pidfd_open(atoi(argv[1]), 0);
if (pidfd == -1) {
perror("pidfd_open");
exit(EXIT_FAILURE);
}

pollfd.fd = pidfd;
pollfd.events = POLLIN;

ready = poll(&pollfd, 1, -1);
if (ready == -1) {
perror("poll");
exit(EXIT_FAILURE);
}

printf("Events (%#x): POLLIN is %sset\n", pollfd.revents,
(pollfd.revents & POLLIN) ? "" : "not ");

close(pidfd);
exit(EXIT_SUCCESS);
}

実験

getrandom

getrandom を実行してみる。 (どちらでも実行できてしまったので今回の趣旨とは異なるが、一応載せておく)

env1

[root@4871234db7f8 workspace]# gcc getrandom.c
[root@4871234db7f8 workspace]# ./a.out
buf = 00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,
ret = 16
buf = 82,92,6f,1b,87,18,1e,8a,d8,ac,7e,5a,f7,9b,59,18,

env2

[root@4b84690c185e workspace]# gcc getrandom.c
[root@4b84690c185e workspace]# ./a.out
buf = 00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,
ret = 16
buf = 19,95,66,4c,43,bb,5f,83,b5,44,2e,6c,db,92,90,66,

pidfd_open

sleep するだけのプロセスを起動して pidfd_open で file descriptor を取得してみる。

#include <stdio.h>
#include <unistd.h>

int main() {
printf("pid: %d\n", getpid());
sleep(1000);
}

env1

host OS が centos でシステムコールが実装されていないのでエラーになる。

[root@4871234db7f8 workspace]# ./sleep
pid: 80
[root@4871234db7f8 workspace]# ./pidfd_open 80
pid: 80
pidfd_open: Function not implemented

v3.10 では do_syscall64 がなく、arch/x86/kernel/entry_64.S で syscall table を直接 call している。 直前で NR_syscall のバリデーションをしているのでここでエラーハンドリング用の badsys に飛ばされる。

飛ばされたあとはリターンしてユーザプロセスに実行が遷移し、libc の syscall() でエラーハンドリングされているはず。

env2

centos のコンテナではライブラリが古いので SYS_pidfd_open が定義されておらず、コンパイルエラーになる。

[root@4b84690c185e workspace]# gcc pidfd_open.c -o pidfd_open
pidfd_open.c: In function 'main':
pidfd_open.c:12:22: error: 'SYS_pidfd_open' undeclared (first use in this function)
int pidfd = syscall(SYS_pidfd_open, pid, 0);
^
pidfd_open.c:12:22: note: each undeclared identifier is reported only once for each function it appears in

pidfd_open は 434 番なので自分で定数を定義してコンパイルする。

#include <stdio.h>
#include <stdlib.h>
#include <sys/syscall.h>
#include <unistd.h>

+#define SYS_pidfd_open 434
+
int main(int argc, char *argv[]) {
int pid = getpid();
if (argc != 1) {
pid = atoi(argv[1]);
}
printf("pid: %d\n", pid);
int pidfd = syscall(SYS_pidfd_open, pid, 0);
if (pidfd < 0) {
perror("pidfd_open");
return 1;
}
printf("pidfd: %d\n", pidfd);
sleep(1000);
close(pidfd);
return 0;
}

実行できた。

[root@4b84690c185e workspace]# ./sleep
pid: 165
[root@4b84690c185e workspace]# ./pidfd_open 165
pid: 165
pidfd: 3

このとき、開かれた fd は procfs でみると下記のようにリンクが張られている。

[root@4b84690c185e workspace]# ls -la /proc/166/fd
total 0
dr-x------. 2 root root 0 May 3 05:28 .
dr-xr-xr-x. 9 root root 0 May 3 05:23 ..
lrwx------. 1 root root 64 May 3 05:28 0 -> /dev/pts/2
lrwx------. 1 root root 64 May 3 05:28 1 -> /dev/pts/2
lrwx------. 1 root root 64 May 3 05:28 2 -> /dev/pts/2
lrwx------. 1 root root 64 May 3 05:28 3 -> anon_inode:[pidfd]

pidfd_open + poll

man page にあるコードを使用してプロセスの監視を行ってみる。

env1

実行できないのはわかっているのでスキップ

env2

先ほどと同様に sleep するプロセスを起動して監視してみる。

[root@4b84690c185e workspace]# ./sleep
pid: 185
^C

ctrl+c で終了させると poll が検知して終了した。

[root@4b84690c185e workspace]# ./pidfd_open_poll 185
Events (0x1): POLLIN is set

追加 (pidfd_send_signal)

pidfd_send_signal を使ってみる。

こちらも試すだけなら man page のコードを使うだけで良さそう。

#define _GNU_SOURCE
#include <fcntl.h>
#include <limits.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/syscall.h>
#include <unistd.h>

static int pidfd_send_signal(int pidfd, int sig, siginfo_t *info,
unsigned int flags)
{
return syscall(SYS_pidfd_send_signal, pidfd, sig, info, flags);
}

int main(int argc, char *argv[])
{
int pidfd, sig;
char path[PATH_MAX];
siginfo_t info;

if (argc != 3) {
fprintf(stderr, "Usage: %s <pid> <signal>\n", argv[0]);
exit(EXIT_FAILURE);
}

sig = atoi(argv[2]);

/* Obtain a PID file descriptor by opening the /proc/PID directory
of the target process. */

snprintf(path, sizeof(path), "/proc/%s", argv[1]);

pidfd = open(path, O_RDONLY);
if (pidfd == -1) {
perror("open");
exit(EXIT_FAILURE);
}

/* Populate a 'siginfo_t' structure for use with
pidfd_send_signal(). */

memset(&info, 0, sizeof(info));
info.si_code = SI_QUEUE;
info.si_signo = sig;
info.si_errno = 0;
info.si_uid = getuid();
info.si_pid = getpid();
info.si_value.sival_int = 1234;

/* Send the signal. */

if (pidfd_send_signal(pidfd, sig, &info, 0) == -1) {
perror("pidfd_send_signal");
exit(EXIT_FAILURE);
}

exit(EXIT_SUCCESS);
}

sleep プロセスを作成して SIGTERM(15) を送信したところプロセスを終了させることができた。

[root@4b84690c185e workspace]# ./sleep
pid: 222
Terminated
[root@4b84690c185e workspace]# ./pidfd_send_signal 222 15

まとめ

  • Container では host os のカーネルを共有しているため、期待されるカーネルバージョンの違いによってうまく動作しないアプリケーションがある。
  • Systemcall の実装状況も影響するため、互換性を確認する際には注意が必要。
  • 自分の周囲ではこの問題がそこまで指摘されていない(顕在化していないのは)ホストOS のバージョンが比較的新しいことや新しいシステムコールなどがそこまで使用されていないからかもしれない。
    • python3.9 では pidfd が使われているようなので、centos 上のコンテナで新しい python を使うと問題が起こるかもしれない
  • pidfd + poll を使ったプロセス監視や signal の送信などが単純に面白かった

Rust in Linux part two

· 10 min read

I want to retry using Rust in the linux kernel, which didn't work well before.

Recently, I've come across some new articles about creating LKMs with Rust, so I'm hoping that by following them, it will work well this time.

To-Do

Last time, I tried it on a VM with Fedora, but this time I will use Arch Linux on a physical machine.

Since the default linux package doesn't have the options enabled for using Rust, I need to build the kernel.

grep CONFIG_RUST /usr/src/linux/.config
# No output

I will refer to the article and try to install module, rust_minimal.rs, which is written in the kernel.

Build directory of LKM is as follows:

.
├── Makefile
└── src
├── rust_minimal.rs
└── Makefile
Makefile
KVER ?= $(shell uname -r)
KDIR ?= /usr/lib/modules/$(KVER)/build

RUSTFMT = rustfmt
RUST_FLAGS = CROSS_COMPILE=x86_64-linux-gnu-
RUST_FLAGS += HOSTRUSTC=rustc
RUST_FLAGS += RUSTC=rustc
RUST_FLAGS += BINDGEN=bindgen
RUST_FLAGS += RUSTFMT=$(RUSTFMT)
RUST_FLAGS += RUST_LIB_SRC=$(shell rustc --print sysroot)/lib/rustlib/src/rust/library

default:
$(MAKE) LLVM=1 $(RUST_FLAGS) -C $(KDIR) M=$$PWD/src

install: default
kmodsign sha512 \
/var/lib/shim-signed/mok/MOK.priv \
/var/lib/shim-signed/mok/MOK.der \
src/hello.ko
$(MAKE) -C $(KDIR) M=$$PWD/src modules_install
depmod -A

fmt:
find . -type f -name '*.rs' | xargs $(RUSTFMT)

clean:
$(MAKE) $(RUNST_FLAGS) -C $(KDIR) M=$$PWD/src clean
src/rust_minimal.rs
// SPDX-License-Identifier: GPL-2.0

//! Rust minimal sample.

use kernel::prelude::*;

module! {
type: RustMinimal,
name: "rust_minimal",
author: "Rust for Linux Contributors",
description: "Rust minimal sample",
license: "GPL",
}

struct RustMinimal {
numbers: Vec<i32>,
}

impl kernel::Module for RustMinimal {
fn init(_module: &'static ThisModule) -> Result<Self> {
pr_info!("Rust minimal sample (init)\n");
pr_info!("Am I built-in? {}\n", !cfg!(MODULE));

let mut numbers = Vec::new();
numbers.try_push(72)?;
numbers.try_push(108)?;
numbers.try_push(200)?;

Ok(RustMinimal { numbers })
}
}

impl Drop for RustMinimal {
fn drop(&mut self) {
pr_info!("My numbers are {:?}\n", self.numbers);
pr_info!("Rust minimal sample (exit)\n");
}
}
src/Makefile
obj-m := rust_minimal.o

The following is a record of trial and error.

Method 1: Using AUR's linux-rust (Unsuccessful)

I found a kernel (v6.6) on AUR that seems to have Rust enabled in the kernel build.

It is possible to install the package simply by running paru -S linux-rust.

However, it had some issues issues such as make not having options for multi-threaded build, and building documentation is included by default, and I don't need it.

So I made some adjustments before building.

Build

Retrieve PKGBUILD:

git clone git@github.com:rnestler/archpkg-linux-rust.git

Edit PKGBUILD:

diff --git a/PKGBUILD b/PKGBUILD
index 7f0db73..b859ccc 100644
--- a/PKGBUILD
+++ b/PKGBUILD
@@ -90,8 +90,8 @@ prepare() {

build() {
cd $_srcname
- make LLVM=1 all
- make LLVM=1 htmldocs
+ make LLVM=1 all -j5
+ # make LLVM=1 htmldocs
}

_package() {
@@ -245,7 +245,7 @@ _package-docs() {
pkgname=(
"$pkgbase"
"$pkgbase-headers"
- "$pkgbase-docs"
+ # "$pkgbase-docs"
)
for _p in "${pkgname[@]}"; do
eval "package_$_p() {

Build:

makepkg -s

During the build, an error occurred in _package-headers() when creating the headers package.

The error said that target.json was missing.

_package-headers() {
...
# Rust support
echo "Installing Rust files..."
install -Dt "$builddir/rust" -m644 scripts/target.json
install -Dt "$builddir/rust" -m644 rust/*.rmeta
install -Dt "$builddir/rust" -m644 rust/*.so
install -Dt "$builddir" -m644 ../../rust-toolchain
...
}

Indeed, the scripts/target.json file was missing in the build directory.

target.json seems to be a file used for Rust cross-compile settings and should look like the following (from https://github.com/archlinux/linux):

{
"arch": "x86_64",
"data-layout": "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128",
"features": "-3dnow,-3dnowa,-mmx,+soft-float,+retpoline-external-thunk",
"llvm-target": "x86_64-linux-gnu",
"target-pointer-width": "64",
"emit-debug-gdb-scripts": false,
"frame-pointer": "may-omit",
"stack-probes": {"kind": "none"}
}

I proceeded with the kernel build again, and this time it completed without issues.

Install the generated *.pkg.tar.zst:

sudo pacman -U linux-rust-6.6.10.arch1-1-x86_64.pkg.tar.zst linux-rust-headers-6.6.10.arch1-1-x86_64.pkg.tar.zst

Setup bootloader

For systemd-boot, add the entry:

sudo nvim /boot/loader/entries/linux-rust.conf
/boot/loader/entries/linux-rust.conf
title Arch Linux (linux-rust)
linux /vmlinuz-linux-rust
initrd /initramfs-linux-rust.img
options <options>

For GRUB:

sudo grub-mkconfig -o /boot/grub/grub.cfg

Reboot.

Building LKM

I installed this kernel and attempted to create an LKM.

However, the module build failed (I forgot to record the error log, but in essence, it couldn't resolve dependencies like the core crate).

In the Makefile, RUST_LIB_SRC is set as $(shell rustc --print sysroot)/lib/rustlib/src/rust/library, and checking this directory, I found the core crate and other essential crates were existing.

I suspected that RUST_LIB_SRC was not set correctly, so I investigated how it was used.

Searching in the Linux kernel cloned from GitHub, I found that RUST_LIB_SRC was used in rust/Makefile as follows:

% fd -tf -x grep -Hni --color=always "RUST_LIB_SRC"
./Makefile:584:ifdef RUST_LIB_SRC
./Makefile:585: export RUST_LIB_SRC
./scripts/rust_is_available_test.py:271: result = self.run_script(self.Expected.FAILURE, { "RUST_LIB_SRC": self.missing })
./scripts/rust_is_available.sh:253:rustc_src=${RUST_LIB_SRC:-"$rustc_sysroot/lib/rustlib/src/rust/library"}
./rust/Makefile:45:RUST_LIB_SRC ?= $(rustc_sysroot)/lib/rustlib/src/rust/library
./rust/Makefile:112:rustdoc-core: $(RUST_LIB_SRC)/core/src/lib.rs FORCE
./rust/Makefile:411: $(RUST_LIB_SRC) $(KBUILD_EXTMOD) > \
./rust/Makefile:431:$(obj)/core.o: $(RUST_LIB_SRC)/core/src/lib.rs scripts/target.json FORCE

However, when searching in /usr/lib/modules/$(KVER)/build, I couldn't find rust/ directory in the first place.

% fd -tf -x grep -Hni --color=always "RUST_LIB_SRC"
./Makefile:584:ifdef RUST_LIB_SRC
./Makefile:585: export RUST_LIB_SRC
./scripts/rust_is_available_test.py:271: result = self.run_script(self.Expected.FAILURE, { "RUST_LIB_SRC": self.missing })
./scripts/rust_is_available.sh:253:rustc_src=${RUST_LIB_SRC:-"$rustc_sysroot/lib/rustlib/src/rust/library"}

This is likely the cause of the failed LKM build.

I tried copying the rust/ directory and rebuild the kernel, but the issue persisted.

Method 2: Building the Kernel Yourself (failed)

Although it's a bit cumbersome, let's attempt to build the kernel manually.

Clone the archlinux/linux repository to build the kernel.

Build

Firstly, enable CONFIG_RUST in menuconfig.

There are various dependent options, so search with / to verify and enable them as needed.

make LLVM=1 olddefconfig
make LLVM=1 menuconfig

Once the configuration file is created, proceed with the build.

make LLVM=1 -j5

Install the modules and copy the kernel.

sudo make LLVM=1 modules_install
sudo cp arch/x86_64/boot/bzImage /boot/vmlinuz-linux-rust-enabled

Setup Bootloader

Configure the bootloader as in Method 1.

sudo nvim /boot/loader/entries/linux-rust-enabled.conf
/boot/loader/entries/linux-rust-enabled.conf
title Arch Linux (linux-rust-enabled)
linux /vmlinuz-linux-rust-enabled
initrd /initramfs-linux-rust-enabled.img
options <options>

Create the initramfs.

sudo mkinitcpio -p linux-rust-enabled

Reboot...

It doesn't boot.

When selecting linux-rust-enabled with systemd-boot, the following log appears on the screen, and it hangs.

EFI stub: Loaded initrd from LINUX_EFI_INITRD_MEDIA_GUID device path
EFI stub: Measured initrd data into PCR 9

For now, revert to the regular kernel for investigation.

Upon closer inspection of the log generated during initramfs creation, the following warning caught my attention:

% sudo mkinitcpio -p linux-rust-enabled
==> Building image from preset: /etc/mkinitcpio.d/linux-rust-enabled.preset: 'default'
==> Using configuration file: '/etc/mkinitcpio-rust-enabled.conf'
-> -k /boot/vmlinuz-linux-rust-enabled -c /etc/mkinitcpio-rust-enabled.conf -g /boot/initramfs-linux-rust-enabled.img --microcode /boot/intel-ucode.img
==> Starting build: '6.7.1-arch1'
-> Running build hook: [base]
-> Running build hook: [udev]
-> Running build hook: [autodetect]
-> Running build hook: [modconf]
-> Running build hook: [kms]
-> Running build hook: [keyboard]
-> Running build hook: [keymap]
-> Running build hook: [consolefont]
==> WARNING: consolefont: no font found in configuration
-> Running build hook: [block]
-> Running build hook: [encrypt]
-> Running build hook: [filesystems]
-> Running build hook: [fsck]
==> Generating module dependencies
==> Creating zstd-compressed initcpio image: '/boot/initramfs-linux-rust-enabled.img'
==> WARNING: errors were encountered during the build. The image may not be complete.
==> Building image from preset: /etc/mkinitcpio.d/linux-rust-enabled.preset: 'fallback'
==> Using configuration file: '/etc/mkinitcpio-rust-enabled.conf'
-> -k /boot/vmlinuz-linux-rust-enabled -c /etc/mkinitcpio-rust-enabled.conf -g /boot/initramfs-linux-rust-enabled-fallback.img -S autodetect --microcode /boot/intel-ucode.img
==> Starting build: '6.7.1-arch1'
-> Running build hook: [base]
-> Running build hook: [udev]
-> Running build hook: [modconf]
-> Running build hook: [kms]
-> Running build hook: [keyboard]
-> Running build hook: [keymap]
-> Running build hook: [consolefont]
==> WARNING: consolefont: no font found in configuration
-> Running build hook: [block]
-> Running build hook: [encrypt]
-> Running build hook: [filesystems]
-> Running build hook: [fsck]
==> Generating module dependencies
==> Creating zstd-compressed initcpio image: '/boot/initramfs-linux-rust-enabled-fallback.img'
==> WARNING: errors were encountered during the build. The image may not be complete.

Although warnings like ==> WARNING: consolefont: no font found in configuration were present even with the regular kernel, ==> WARNING: errors were encountered during the build. The image may not be complete. was not present, and ==> Image generation successful was reported.

It seemed like some modules might be missing, and based on the log like RPM 9, I suspected that TPM modules might be necessary.

So, I tried searching for related options in menuconfig and enabling them.

The result was the same log, and the system did not boot correctly.

(I checked journalctl later to see if systemd-boot logs were present, but I couldn't find any relevant logs.)

Method 3: Building the Kernel Yourself (Successful Version)

As it seems there might be missing kernel configs, let's try utilizing the config from linux-rust.

While the linux-rust kernel failed to build modules, it could boot, so it should include the necessary drivers for booting.

Copy the .config file and modify a few options, including the Rust version, then proceed with the build using the archlinux/linux base code.

Build

After placing the config, build again (LLVM=1 is only needed with make -j5 perhaps?).

make LLVM=1 olddefconfig
make LLVM=1 menuconfig
make LLVM=1 -j5
sudo make LLVM=1 modules_install
sudo cp arch/x86_64/boot/bzImage /boot/vmlinuz-linux-rust-enabled

While reviewing the config, I realized that nvme driver is not enabled in the configuration file that it used when failed. So, I suspect that caused the boot failure.

Setup Bootloader

Configure the bootloader as before.

sudo nvim /boot/loader/entries/linux-rust-enabled.conf
/boot/loader/entries/linux-rust-enabled.conf
title Arch Linux (linux-rust-enabled)
linux /vmlinuz-linux-rust-enabled
initrd /initramfs-linux-rust-enabled.img
options <options>

Create the initramfs (I omitted creating the fallback image this time because unnecessary kernel config was enabled, and the fallback image size was too large to fit in the /boot partition. There is room for improvement).

% sudo mkinitcpio -p linux-rust-enabled
==> Building image from preset: /etc/mkinitcpio.d/linux-rust-enabled.preset: 'default'
==> Using configuration file: '/etc/mkinitcpio-rust-enabled.conf'
-> -k /boot/vmlinuz-linux-rust-enabled -c /etc/mkinitcpio-rust-enabled.conf -g /boot/initramfs-linux-rust-enabled.img --microcode /boot/intel-ucode.img
==> Starting build: '6.7.1-arch1'
-> Running build hook: [base]
-> Running build hook: [udev]
-> Running build hook: [autodetect]
-> Running build hook: [modconf]
-> Running build hook: [kms]
-> Running build hook: [keyboard]
==> WARNING: Possibly missing firmware for module: 'xhci_pci'
-> Running build hook: [keymap]
-> Running build hook: [consolefont]
==> WARNING: consolefont: no font found in configuration
-> Running build hook: [block]
-> Running build hook: [encrypt]
-> Running build hook: [filesystems]
-> Running build hook: [fsck]
==> Generating module dependencies
==> Creating zstd-compressed initcpio image: '/boot/initramfs-linux-rust-enabled.img'
==> Image generation successful

This time, it seems to have been created successfully, and after a reboot, it booted correctly.

Creating LKM

As mentioned earlier, I try to build rust_minimal.rs as a module.

When I run make in the module directory, it succeeds this time.

% make
make LLVM=1 CROSS_COMPILE=x86_64-linux-gnu- HOSTRUSTC=rustc RUSTC=rustc BINDGEN=bindgen RUSTFMT=rustfmt RUST_LIB_SRC=/home/mori/.rustup/toolchains/1.73.0-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library -C /usr/lib/modules/6.7.1-arch1/build M=$PWD/src
make[1]: Entering directory '/home/mori/workspace/archlinux-kernel'
RUSTC [M] /home/mori/workspace/rust-module/module/src/rust_minimal.o
MODPOST /home/mori/workspace/rust-module/module/src/Module.symvers
CC [M] /home/mori/workspace/rust-module/module/src/rust_minimal.mod.o
LD [M] /home/mori/workspace/rust-module/module/src/rust_minimal.ko
BTF [M] /home/mori/workspace/rust-module/module/src/rust_minimal.ko
die__process_class: tag not supported 0x33 (variant_part)!
die__process_class: tag not supported 0x2f (template_type_parameter)!
make[1]: Leaving directory '/home/mori/workspace/archlinux-kernel'

Load and unload the module.

sudo insmod src/rust_minimal.ko
sudo rmmod rust_minimal
[  160.098129] rust_minimal: loading out-of-tree module taints kernel.
[ 160.098132] rust_minimal: module verification failed: signature and/or required key missing - tainting kernel
[ 160.098596] rust_minimal: Rust minimal sample (init)
[ 160.098598] rust_minimal: Am I built-in? false
[ 180.365691] rust_minimal: My numbers are [72, 108, 200]
[ 180.365696] rust_minimal: Rust minimal sample (exit)

Finally, it worked!

Summary

Looking back, there were three main challenges I encountered while creating the Rust module:

  • Using a kernel with the CONFIG_RUST option enabled.
  • Writing the Makefile for the Rust-based Loadable Kernel Module (LKM).
  • Understanding the usage of Rust's toolchain.

Fortunately, I was able to overcome these hurdles this time.

Moving forward, I am eager to explore and experiment with various aspects of Rust LKMs.

It might be interesting to try this on Ubuntu, considering that recent Ubuntu kernels already have the CONFIG_RUST option enabled.

Learning how to analyze programs and kernels with perf

· 5 min read

Overview

One of the tools that seemed useful but wasn't used is perf. This time, let's learn the basics of using perf.

Goal

  • Analyze a application using perf.
  • Attempt to create a flamegraph because it seems possible.
  • Investigate if there is anything usable in a GUI.

Preparation

Install the necessary packages. If only perf is needed, it can be installed with the following:

sudo pacman -S perf

For Arch Linux, various tools are included in the linux-tools group, so it seems good to install them with:

paru -Sg linux-tools

Install the required packages for creating flamegraphs (available in AUR):

paru -S flamegraph

To analyze the kernel with perf, you need to change a kernel parameter called perf_event_paranoid. This parameter controls access to performance events, and its default value is 2. However, this prevents capturing kernel events, so we need to change it to -1.

FYI: Kernel Documentation - perf-security

cat /proc/sys/kernel/perf_event_paranoid
echo -1 | sudo tee /proc/sys/kernel/perf_event_paranoid

fio

In this article, we will use fio, a tool for taking I/O benchmarks, as a test application.

sudo pacman -S fio

While researching how to use fio, it was found that there is a configuration item called ioengine. To see how the behavior changes when this ioengine is changed, we will analyze it with perf.

By the way, looking at the man page for fio reveals that there are many different ioengine options that can be configured.

FYI: fio Man Page

Although there is no particular preference, we will try libaio, sync, and mmap this time.

Memo about perf

  • Use perf stat to display metrics such as execution time and CPU usage.
    • For example, the result when running pwd is shown below:
      % perf stat pwd
      /home/mori/workspace/perf

      Performance counter stats for 'pwd':

      1.83 msec task-clock:u # 0.161 CPUs utilized
      0 context-switches:u # 0.000 /sec
      0 cpu-migrations:u # 0.000 /sec
      67 page-faults:u # 36.526 K/sec
      317,835 cycles:u # 0.173 GHz
      232,109 instructions:u # 0.73 insn per cycle
      52,250 branches:u # 28.485 M/sec
      3,235 branch-misses:u # 6.19% of all branches

      0.011358294 seconds time elapsed

      0.000000000 seconds user
      0.003322000 seconds sys
      • branch-misses refers to failed speculative execution.
      • context-switches does not indicate the number of times the scheduler switches.
      • It remained 0 even with the sleep command or a program using pthread.
  • perf bench provides several benchmark tools.
    % perf bench
    Usage:
    perf bench [<common options>] <collection> <benchmark> [<options>]

    # List of all available benchmark collections:

    sched: Scheduler and IPC benchmarks
    syscall: System call benchmarks
    mem: Memory access benchmarks
    numa: NUMA scheduling and MM benchmarks
    futex: Futex stressing benchmarks
    epoll: Epoll stressing benchmarks
    internals: Perf-internals benchmarks
    breakpoint: Breakpoint benchmarks
    uprobe: uprobe benchmarks
    all: All benchmarks
  • For detailed program analysis, use perf record and view the results with perf report.
    • When you run perf record, the results are saved in a file called perf.data (can be changed with the -o option).
    • You can specify events with the -e option; check the available events with perf list.
    • Adding the -g option allows you to obtain a call graph, which is necessary for creating a flamegraph later.
    • Use perf report -i with the perf.data obtained from perf record to display the results.
  • perf top is similar to a real-time perf report.
  • perf script outputs the results of perf record as a script.
    • It shows timestamps, event names, and event details.
    • Flamegraphs are created based on this script.
  • Use perf annotate to annotate assembly code.
  • perf diff is used to compare perf.data files.
    • It can be used to verify the effects when making performance improvements.
  • perf kvm is used to profile KVM.
    • You can use perf kvm --guest record to profile the guest machine, but I had trouble getting it to work, so I'll look into it later.

Example

Let's use perf to analyze fio.

The command to capture the perf.data is as follows:

perf record -o /tmp/perf_libaio.data -g fio configs/seq_read_libaio.ini

To create a flamegraph from the obtained performance data:

perf script -i /tmp/per

f_libaio.data | stackcollapse-perf.pl | flamegraph.pl > images/perf_libaio.svg

Note: Be sure to include the -g option when recording with perf record to obtain the call graph.

Flamegraphs for changing the ioengine to libaio, sync, and mmap respectively:

libaio:

sync:

mmap:

There are many parts marked as "unknown," but this can likely be resolved by building fio with debugging information.

For a more thorough investigation, consider rebuilding and using tools like flamegraphs, perf report, and perf diff to examine the differences and confirm improvements when making changes.

Other Tools

A relatively recent tool discovered is hotspot, a tool for displaying perf results in a GUI, supporting flamegraphs as well. (Found in AUR for Arch, but encountered a build error when attempting to install)

A tool introduced on Qiita while researching perf. When you upload the test.perf obtained with the following command, the results are displayed as shown in the image below:

perf script -i /tmp/perf_libaio.data -F +pid > test.perf

Wrap Up

It seems beneficial to actively use perf when investigating bottlenecks or obtaining hints for improvements due to OS updates or application updates.

Setting Up RAID Using mdadm

· 8 min read

Objective

I want to set up RAID on an external HDD with Raspberry Pi.

Background

RAID is a technology that combines multiple storage devices to use as a single, highly reliable storage. I had a vague idea of what it does, but this time I try to research the principles and set up RAID to use as a file server.

Below is a simple explanation of each RAID level. You can find more detailed and comprehensible information by looking at sources like the Arch wiki, but I will write it here for the sake of learning as well.

RAID 0

RAID 0 is a RAID that does not employ any redundancy. Data is distributed and saved, and since the bandwidth is large, read and write operations can be performed quickly if things go well.

Since it does not improve reliability through redundancy, I was not sure about its practical use cases. But upon reading the Arch wiki, it says that it can be used for swap partitions and other scenarios where "the speed increase is worth the possibility of data loss". As large capacity memory is used these days, I really don't know the practical use cases as swap areas are often not created.

RAID 1

RAID 1 is a RAID that improves reliability by writing exactly the same data to multiple disks. Redundancy is provided, but there is a problem with the inefficient use of storage. Basically, when setting up RAID 1 using two storages, the usable capacity becomes half of the total.

Since there is no problem if either of the storages is alive, it is overwhelmingly reassuring compared to RAID 0.

RAID 1+0 (RAID 10)

In RAID 10, RAID 1 is combined with RAID 0 to improve performance while providing redundancy. The redundancy method is the same as RAID 1, so the storage usage efficiency remains poor.

It has improved fault tolerance compared to RAID 1, and there is no problem if two fail simultaneously, as long as they are from different RAID 1 groups. Of course, if two from the same RAID 1 group fail at the same time, it's over.

RAID 5

RAID 5 distributes and stores data, and creates parity blocks for redundancy. The capacity of parity is at most one storage, so if you set up RAID 5 with N storage devices, the usable capacity becomes (N - 1) / N.

Parity is calculated using XOR, so if only one storage fails, you can restore it. However, if two or more fail simultaneously, restoration is not possible, so depending on the reliability of the storage, the number and size of combinations, it is not recommended. If a disk fails, you add a new disk and perform restoration, but a large amount of reading and writing is performed during restoration, so there is a possibility of failure at that time. Again, according to the Arch wiki, it is not recommended in the recent storage industry.

RAID 6

RAID 6 is designed to allow for restoration even if up to two storage devices fail simultaneously by increasing the parity of RAID 5. A minimum of 4 storage devices is required, and the usage efficiency of capacity becomes (N - 2) / N.

It seems that parity is done in two ways, XOR and Reed-Solomon codes. To be honest, I don’t really understand Reed-Solomon codes. However, they are used for a specific reason. For example, in the case below, if the data of C1 and C2 is lost, XOR calculations alone cannot determine the values of C1 and C2. Reed-Solomon codes are introduced to address this issue and help in recovering the lost values.

My environment

Now that we understand the basic principles of RAID, let’s write about the environment for this time.

  • HDD: 512 GB x 5
  • Raspberry Pi 4B
    • OS: Ubuntu 23.04

For the RAID level, I will go with RAID 5. It is said that it is not recommended these days, but I believe it will be fine since the disk capacity is not that large.

RAID Configuration

I will use mdadm, which seems to be a standard for configuring RAID on Linux.

First, the state of the devices in the initial state looked like this.

lsblk
NAME        MAJ:MIN RM   SIZE RO TYPE MOUNTPOINTS
loop0 7:0 0 67.7M 1 loop /snap/core22/637
loop1 7:1 0 46.4M 1 loop /snap/snapd/19127
loop2 7:2 0 161.2M 1 loop /snap/lxd/24922
loop3 7:3 0 68.5M 1 loop /snap/core22/753
loop4 7:4 0 157.6M 1 loop /snap/lxd/24851
loop5 7:5 0 46.4M 1 loop /snap/snapd/19365
sda 8:0 0 465.8G 0 disk
└─sda1 8:1 0 465.8G 0 part
sdb 8:16 0 465.8G 0 disk
└─sdb1 8:17 0 465.8G 0 part
sdc 8:32 0 465.8G 0 disk
└─sdc1 8:33 0 465.8G 0 part
sdd 8:48 0 465.8G 0 disk
└─sdd1 8:49 0 465.8G 0 part
sde 8:64 0 465.8G 0 disk
└─sde1 8:65 0 465.8G 0 part
mmcblk0 179:0 0 116.4G 0 disk
├─mmcblk0p1 179:1 0 256M 0 part /boot/firmware
└─mmcblk0p2 179:2 0 116.1G 0 part /

/dev/sda through /dev/sde are the target HDDs, so we create a RAID device with mdadm.

sudo mdadm --create --verbose /dev/md0 --level=5 --raid-devices=5 /dev/sda /dev/sdb /dev/sdc /dev/sdd /dev/sde
mdadm: layout defaults to left-symmetric
mdadm: layout defaults to left-symmetric
mdadm: chunk size defaults to 512K
mdadm: partition table exists on /dev/sda
mdadm: partition table exists on /dev/sda but will be lost or
meaningless after creating array
mdadm: partition table exists on /dev/sdb
mdadm: partition table exists on /dev/sdb but will be lost or
meaningless after creating array
mdadm: partition table exists on /dev/sdc
mdadm: partition table exists on /dev/sdc but will be lost or
meaningless after creating array
mdadm: partition table exists on /dev/sdd
mdadm: partition table exists on /dev/sdd but will be lost or
meaningless after creating array
mdadm: partition table exists on /dev/sde
mdadm: partition table exists on /dev/sde but will be lost or
meaningless after creating array
mdadm: size set to 488254464K
mdadm: automatically enabling write-intent bitmap on large array
Continue creating array? y
mdadm: Defaulting to version 1.2 metadata
mdadm: array /dev/md0 started.

You should see something like this when you look at /proc/mdstat.

cat /proc/mdstat
Personalities : [linear] [multipath] [raid0] [raid1] [raid6] [raid5] [raid4] [raid10]
md0 : active raid5 sde[5] sdd[3] sdc[2] sda[0] sdb[1]
1953017856 blocks super 1.2 level 5, 512k chunk, algorithm 2 [5/5] [UUUUU]
bitmap: 1/4 pages [4KB], 65536KB chunk

unused devices: <none>

Format /dev/md0 as ext4.

sudo mkfs.ext4 /dev/md0
mke2fs 1.47.0 (5-Feb-2023)
Creating filesystem with 488254464 4k blocks and 122068992 inodes
Filesystem UUID: 65815302-0210-4b7c-8496-a3711f5ccb2a
Superblock backups stored on blocks:
32768, 98304, 163840, 229376, 294912, 819200, 884736, 1605632, 2654208,
4096000, 7962624, 11239424, 20480000, 23887872, 71663616, 78675968,
102400000, 214990848

Allocating group tables: done
Writing inode tables: done
Creating journal (262144 blocks): done
Writing superblocks and filesystem accounting information: done

The mount position will be /mnt/raid5.

sudo mkdir /mnt/raid5
sudo mount /dev/md0 /mnt/raid5/
lsblk
NAME        MAJ:MIN RM   SIZE RO TYPE  MOUNTPOINTS
loop0 7:0 0 67.7M 1 loop /snap/core22/637
loop1 7:1 0 46.4M 1 loop /snap/snapd/19127
loop2 7:2 0 161.2M 1 loop /snap/lxd/24922
loop3 7:3 0 68.5M 1 loop /snap/core22/753
loop4 7:4 0 157.6M 1 loop /snap/lxd/24851
loop5 7:5 0 46.4M 1 loop /snap/snapd/19365
sda 8:0 0 465.8G 0 disk
└─md0 9:0 0 1.8T 0 raid5 /mnt/raid5
sdb 8:16 0 465.8G 0 disk
└─md0 9:0 0 1.8T 0 raid5 /mnt/raid5
sdc 8:32 0 465.8G 0 disk
└─md0 9:0 0 1.8T 0 raid5 /mnt/raid5
sdd 8:48 0 465.8G 0 disk
└─md0 9:0 0 1.8T 0 raid5 /mnt/raid5
sde 8:64 0 465.8G 0 disk
└─md0 9:0 0 1.8T 0 raid5 /mnt/raid5
mmcblk0 179:0 0 116.4G 0 disk
├─mmcblk0p1 179:1 0 256M 0 part /boot/firmware
└─mmcblk0p2 179:2 0 116.1G 0 part /
df -h
Filesystem      Size  Used Avail Use% Mounted on
tmpfs 380M 8.3M 371M 3% /run
/dev/mmcblk0p2 115G 6.3G 104G 6% /
tmpfs 1.9G 0 1.9G 0% /dev/shm
tmpfs 5.0M 0 5.0M 0% /run/lock
tmpfs 100M 0 100M 0% /tmp
tmpfs 32M 496K 32M 2% /var/log
/dev/mmcblk0p1 253M 152M 101M 61% /boot/firmware
tmpfs 380M 4.0K 380M 1% /run/user/1000
/dev/md0 1.8T 2.1M 1.7T 1% /mnt/raid5

After rebooting, sometimes the device name becomes /dev/md127 instead of /dev/md0, so after a little research, it seems to be good to modify /etc/mdadm/mdadm.conf as follows.

sudo mdadm --detail --scan | sudo tee -a /etc/mdadm/mdadm.conf
+ ARRAY /dev/md/ubuntu:0 metadata=1.2 name=ubuntu:0 UUID=098f8892:ef715a43:dd38085d:46fb97c3

Update initramfs. (I don't know if it's necessary as the Boot partition is not on RAID, but just in case)

sudo update-initramfs -u

Trouble Shooting: Auto-mount

Initially, I had added an entry for /mnt/raid5 in /etc/fstab, but there was a problem where it tried to mount before the external HDD was started and the device was recognized.

As a solution, I used systemd mount. With systemd mount, you can set detailed execution timing, so I configured it to execute after /dev/md0 is recognized.

sudo vim /etc/systemd/system/mnt-raid5.mount
[Unit]
Description=Mount RAID Array
After=dev-md0.device
Requires=dev-md0.device

[Mount]
What=/dev/md0
Where=/mnt/raid5
Type=ext4
Options=defaults

[Install]
WantedBy=multi-user.target
sudo systemctl enable mnt-raid5.mount

Deploying n8n on Raspberry Pi for Customized RSS Feed

· 4 min read

Objective

Deploy n8n on Raspberry Pi 4B to send RSS feeds to Discord.

Background

Until recently, I was using IFTTT for the same purpose, but as of May 23, 2023, the number of applications that can be run on the free plan has been limited to two. There are similar services like Zapier, make, Readybot.io, and as of May 27, 2023, the usage scope is as follows:

  • Zapier
    • Up to 5 applications (Zaps)
    • Can set up to one trigger, one action
    • 100 tasks/month
    • Can be run at a minimum interval of 15 minutes
  • make
    • Up to 2 applications (scenarios)
    • 1000 ops/month
    • Can be run at a minimum interval of 15 minutes
  • Readybot.io
    • Specialized for Discord
    • Up to 5 applications (bots)
    • Can be run at a minimum interval of 10 minutes

Zapier and make did not meet my needs due to the daily execution limit and the number of applications that can be created. Readybot.io was perfect for my purpose, managing multiple RSS feeds with a single bot was quite good. However, it lacked flexibility, such as being unable to customize the content of notifications to Discord.

So this time, I am going to use the OSS n8n (github). While there is no free plan when hosting with n8n, it can be used quite freely when self-hosting. There is the cost of electricity and the effort of managing it yourself, but I'm going to give it a try simply because I'm interested.

Deploying n8n

This time, I'm using Docker, so you can deploy with just the following command:

docker run -d --rm --name n8n -p 5678:5678 -v ~/.n8n:/home/node/.n8n docker.n8n.io/n8nio/n8n

If you can access http://[ip]:5678/ and see the login screen, then the deployment was successful. I believe the screen was Sign Up on the first access.

Creating the Workflow

If you have experience using services like IFTTT, creating the workflow should be straightforward. You run it at your preferred timing with Schedule Trigger and specify the target URL with RSS Read. Once you register the webhook of the Discord channel in Discord, it's complete.

However, when I first assembled the nodes as below, it ended up sending all the information outputted by RSS Read to Discord each time.

In other words, it wasn't comparing the content obtained at the last execution and distributing only the new content.

Here is the corrected workflow.

The change is the addition of the Code node. In Code, you can write JS code, and if you look at the Documentation, you will see that n8n provides functions or variables. This time, I used $getWorkflowStaticData(type) to record the last execution and only output new items.

const workflowStaticData = $getWorkflowStaticData('global');
const lastRssLog = workflowStaticData.lastRssLog;

if (lastRssLog == null) {
workflowStaticData.lastRssLog = $('RSS Read').first();
return $('RSS Read').all().reverse();
}

let ret = []

for (let item of $('RSS Read').all()) {
if (item.json['guid'] == lastRssLog.json['guid']) {
break;
}
ret.push(item);
}

workflowStaticData.lastRssLog = $('RSS Read').first();

return ret.reverse();

Raspberry Pi Resource Usage

As far as I have researched, there doesn't seem to be any issues with the specs, but just in case, I checked the resource usage when an event occurs. I'm running the RSS check at 5-minute intervals for two applications.

During normal times:

At the time of events:

Ending

Although I checked out various other services similar to IFTTT, considering the limitations of the free version and the level of freedom, this setup seems to be the best at the moment. I also found a similar open source application called Huginn, but I had to give up because I couldn't run the Docker image due to CPU architecture issues... I've been unable to use containers due to architecture issues lately, but I wonder if I can solve this by rebuilding them once I get better at using Docker.

Rust in Linux

· 8 min read

Purpose

Since Linux v6.1, Rust can be used in the kernel. So, I wanted to try rust-in-linux!

Initial goals:

  • Create an LKM
  • Add a system call and call a handler written in Rust (integrate it into the kernel)

Although I couldn't get it to work during holidays and it doesn't seem easy to use for now, I'll record it for future reference. Please refer to references for pages that may be useful as the kernel may be a fork, so be aware of differences in environment and version.

Background

Recently, there has been a growing movement to write software in Rust. Rust is a programming language that combines memory safety and performance, and it is attracting attention as a programming language for various software.

It is well known that Rust is used in various software such as Dropbox and Discord.

There is a trend to adopt Rust for system software such as operating systems, and it seems that Windows has already undergone significant rewriting. It hasn't been released yet.

It also discusses adoption and influence in Android.

Now, in Linux, which is the main topic, it can be used from v6.1.

It doesn't seem to be considering rewriting the Linux kernel itself at this time, but it seems that they plan to gradually introduce it, starting with drivers. It is often pointed out in vulnerability analysis of Linux that there are many bugs in drivers rather than in the main kernel, so it seems good to improve that area. Since it has come to this, I believe it's time for me to become familiar with Rust, so I will attempt to use Rust by referring to the documentation in the Linux kernel.

Preparation

Since there is no random environment that can be broken, I will experiment on a VM this time. The VM settings are as follows.

  • CPU: 6 cores, x86_64
  • Mem: 8 GB
  • Disk: 75 GB
  • OS: Fedora 38 server

First, make sure you can build the kernel in a Fedora environment.

Source Code

Download the kernel code. The working directory is $HOME/linux-6.3.1/.

curl -O https://mirrors.edge.kernel.org/pub/linux/kernel/v6.x/linux-6.3.1.tar.xz
tar xf linux-6.3.1.tar.xz

Install Required Software

Refer to Documents - rust/quick-start and install the necessary software. First, install rustup.

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

Change the rustc version.

cd /path/to/kernel-src
rustup override set `scripts/min-tool-version.sh rustc`
rustup component add rust-src

Install clang.

sudo dnf install clang

Install bindgen. This software is necessary to integrate C and Rust.

cargo install --locked --version `scripts/min-tool-version.sh bindgen` bindgen

Install other necessary software that is not written in quick-start.

sudo dnf install kernel-devel llvm lld dwarves zstd ncurses-devel

If you don't install dwarves, it will result in something like:

BTF: .tmp_vmlinux.btf: pahole (pahole) is not available
Failed to generate BTF for vmlinux

Kernel Build

Build the kernel using LLVM.

make LLVM=1 -j7
sudo make modules_install && sudo make install
sudo grub2-mkconfig -o /boot/grub2/grub.cfg

If you're not sure if it went well after executing, check with echo $?

Reboot and Check

reboot

If you are told bad shim signature when selecting a custom kernel from the Grub menu, Secure Boot is getting in the way, so enter the EFI settings and disable it.

$ uname -a
Linux localhost.localdomain 6.3.1 #4 SMP PREEMPT_DYNAMIC Fri May 5 15:55:31 JST 2023 x86_64 GNU/Linux

I was able to run the kernel compiled with LLVM for now.

Make Rust Available

From here on, it's the part that didn't work well this time, but I'll record it anyway.

You need to modify .config.

make menuconfig

Modification 1: Enable Rust support.

  • General setup
    • Rust support

Modification 2: Add sample code in samples/rust/* as kernel modules.

  • Kernel hacking
    • Sample kernel code
      • Rust samples
        • Minimal
        • Printing macros

The following seems to fail again after rebuilding. Output of make LLVM=1 rustavailable seemed to be okay, so it doesn't seem to be a problem with tool installation or path specification.

[mmori@localhost linux-6.3.1]$ make LLVM=1 -j6
DESCEND objtool
CALL scripts/checksyscalls.sh
INSTALL libsubcmd_headers
BINDGEN rust/bindings/bindings_generated.rs
thread 'main' panicked at '"ftrace_branch_data_union_(anonymous_at__/_/include/linux/compiler_types_h_146_2)" is not a valid Ident', /home/mmori/.cargo/registry/src/github.com-1ecc6299db9ec823/proc-macro2-1.0.56/src/fallback.rs:811:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
make[1]: *** [rust/Makefile:303: rust/bindings/bindings_generated.rs] Error 1
make[1]: *** Deleting file 'rust/bindings/bindings_generated.rs'
make: *** [Makefile:1292: prepare] Error 2
[mmori@localhost linux-6.3.1]$ make LLVM=1 -j6 RUST_BACKTRACE=1
DESCEND objtool
CALL scripts/checksyscalls.sh
INSTALL libsubcmd_headers
BINDGEN rust/bindings/bindings_generated.rs
thread 'main' panicked at '"ftrace_branch_data_union_(anonymous_at__/_/include/linux/compiler_types_h_146_2)" is not a valid Ident', /home/mmori/.cargo/registry/src/github.com-1ecc6299db9ec823/proc-macro2-1.0.56/src/fallback.rs:811:9
stack backtrace:
0: rust_begin_unwind
at /rustc/a8314ef7d0ec7b75c336af2c9857bfaf43002bfc/library/std/src/panicking.rs:584:5
1: core::panicking::panic_fmt
at /rustc/a8314ef7d0ec7b75c336af2c9857bfaf43002bfc/library/core/src/panicking.rs:142:14
2: proc_macro2::fallback::Ident::_new
3: proc_macro2::Ident::new
4: bindgen::ir::context::BindgenContext::rust_ident
5: <bindgen::ir::comp::CompInfo as bindgen::codegen::CodeGenerator>::codegen
6: <bindgen::ir::ty::Type as bindgen::codegen::CodeGenerator>::codegen
7: <bindgen::ir::item::Item as bindgen::codegen::CodeGenerator>::codegen
8: <bindgen::ir::comp::CompInfo as bindgen::codegen::CodeGenerator>::codegen
9: <bindgen::ir::ty::Type as bindgen::codegen::CodeGenerator>::codegen
10: <bindgen::ir::item::Item as bindgen::codegen::CodeGenerator>::codegen
11: <bindgen::ir::module::Module as bindgen::codegen::CodeGenerator>::codegen
12: <bindgen::ir::item::Item as bindgen::codegen::CodeGenerator>::codegen
13: bindgen::ir::context::BindgenContext::gen
14: bindgen::Builder::generate
15: std::panicking::try
16: bindgen::main
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.
make[1]: *** [rust/Makefile:303: rust/bindings/bindings_generated.rs] Error 1
make[1]: *** Deleting file 'rust/bindings/bindings_generated.rs'
make: *** [Makefile:1292: prepare] Error 2
[mmori@localhost linux-6.3.1]$ make LLVM=1 -j6 RUST_BACKTRACE=full
DESCEND objtool
CALL scripts/checksyscalls.sh
INSTALL libsubcmd_headers
BINDGEN rust/bindings/bindings_generated.rs
thread 'main' panicked at '"ftrace_branch_data_union_(anonymous_at__/_/include/linux/compiler_types_h_146_2)" is not a valid Ident', /home/mmori/.cargo/registry/src/github.com-1ecc6299db9ec823/proc-macro2-1.0.56/src/fallback.rs:811:9
stack backtrace:
0: 0x55b400da7ffd - std::backtrace_rs::backtrace::libunwind::trace::hb729d9642bb971eb
at /rustc/a8314ef7d0ec7b75c336af2c9857bfaf43002bfc/library/std/src/../../backtrace/src/backtrace/libunwind.rs:93:5
1: 0x55b400da7ffd - std::backtrace_rs::backtrace::trace_unsynchronized::h373bb774579df5c7
at /rustc/a8314ef7d0ec7b75c336af2c9857bfaf43002bfc/library/std/src/../../backtrace/src/backtrace/mod.rs:66:5
2: 0x55b400da7ffd - std::sys_common::backtrace::_print_fmt::hfbd4e92d240c89bb
at /rustc/a8314ef7d0ec7b75c336af2c9857bfaf43002bfc/library/std/src/sys_common/backtrace.rs:66:5
3: 0x55b400da7ffd - <std::sys_common::backtrace::_print::DisplayBacktrace as core::fmt::Display>::fmt::h8f618991fbf64972
at /rustc/a8314ef7d0ec7b75c336af2c9857bfaf43002bfc/library/std/src/sys_common/backtrace.rs:45:22
4: 0x55b400dcdcfc - core::fmt::write::hc69b5b640d88cce8
at /rustc/a8314ef7d0ec7b75c336af2c9857bfaf43002bfc/library/core/src/fmt/mod.rs:1196:17
5: 0x55b400da47d1 - std::io::Write::write_fmt::h3403cef06a24a303
at /rustc/a8314ef7d0ec7b75c336af2c9857bfaf43002bfc/library/std/src/io/mod.rs:1654:15
6: 0x55b400da97d5 - std::sys_common::backtrace::_print::h368f27cdedea0e52
at /rustc/a8314ef7d0ec7b75c336af2c9857bfaf43002bfc/library/std/src/sys_common/backtrace.rs:48:5
7: 0x55b400da97d5 - std::sys_common::backtrace::print::ha105c9cf5a64cd17
at /rustc/a8314ef7d0ec7b75c336af2c9857bfaf43002bfc/library/std/src/sys_common/backtrace.rs:35:9
8: 0x55b400da97d5 - std::panicking::default_hook::{{closure}}::h48ed2c3707d5e20e
at /rustc/a8314ef7d0ec7b75c336af2c9857bfaf43002bfc/library/std/src/panicking.rs:295:22
9: 0x55b400da9449 - std::panicking::default_hook::h8744fc5cea5e3110
at /rustc/a8314ef7d0ec7b75c336af2c9857bfaf43002bfc/library/std/src/panicking.rs:314:9
10: 0x55b400da9da8 - std::panicking::rust_panic_with_hook::hc82286af2030e925
at /rustc/a8314ef7d0ec7b75c336af2c9857bfaf43002bfc/library/std/src/panicking.rs:698:17
11: 0x55b400da9c57 - std::panicking::begin_panic_handler::{{closure}}::h1c15057c2f09081f
at /rustc/a8314ef7d0ec7b75c336af2c9857bfaf43002bfc/library/std/src/panicking.rs:588:13
12: 0x55b400da84b4 - std::sys_common::backtrace::__rust_end_short_backtrace::h65de906a5330f8da
at /rustc/a8314ef7d0ec7b75c336af2c9857bfaf43002bfc/library/std/src/sys_common/backtrace.rs:138:18
13: 0x55b400da9989 - rust_begin_unwind
at /rustc/a8314ef7d0ec7b75c336af2c9857bfaf43002bfc/library/std/src/panicking.rs:584:5
14: 0x55b400ba06e3 - core::panicking::panic_fmt::h741cfbfc95bc6112
at /rustc/a8314ef7d0ec7b75c336af2c9857bfaf43002bfc/library/core/src/panicking.rs:142:14
15: 0x55b400d82cbb - proc_macro2::fallback::Ident::_new::hd54a7f37b4be5ac2
16: 0x55b400d84132 - proc_macro2::Ident::new::h5b24a800de987e3d
17: 0x55b400c87dd2 - bindgen::ir::context::BindgenContext::rust_ident::hfb669cd12fde5045
18: 0x55b400c97543 - <bindgen::ir::comp::CompInfo as bindgen::codegen::CodeGenerator>::codegen::hdac53e6a48364ded
19: 0x55b400cb2df3 - <bindgen::ir::ty::Type as bindgen::codegen::CodeGenerator>::codegen::h9ea78383a1519d12
20: 0x55b400c24993 - <bindgen::ir::item::Item as bindgen::codegen::CodeGenerator>::codegen::h7f1cb457e8b5962b
21: 0x55b400c9a76e - <bindgen::ir::comp::CompInfo as bindgen::codegen::CodeGenerator>::codegen::hdac53e6a48364ded
22: 0x55b400cb2df3 - <bindgen::ir::ty::Type as bindgen::codegen::CodeGenerator>::codegen::h9ea78383a1519d12
23: 0x55b400c24993 - <bindgen::ir::item::Item as bindgen::codegen::CodeGenerator>::codegen::h7f1cb457e8b5962b
24: 0x55b400c7c063 - <bindgen::ir::module::Module as bindgen::codegen::CodeGenerator>::codegen::hcaa3f952aaaebcd2
25: 0x55b400c24963 - <bindgen::ir::item::Item as bindgen::codegen::CodeGenerator>::codegen::h7f1cb457e8b5962b
26: 0x55b400c8b3f3 - bindgen::ir::context::BindgenContext::gen::h8d593441c41f4c9e
27: 0x55b400c42e7a - bindgen::Builder::generate::hc6be0353ef288055
28: 0x55b400bbf63f - std::panicking::try::hfac0a9d96478463b
29: 0x55b400ba9766 - bindgen::main::h66dca222f748f7fb
30: 0x55b400bc0353 - std::sys_common::backtrace::__rust_begin_short_backtrace::he9f5e905c5b4f99b
31: 0x55b400bab539 - std::rt::lang_start::{{closure}}::h309e4f396102cd0f
32: 0x55b400d9f12e - core::ops::function::impls::<impl core::ops::function::FnOnce<A> for &F>::call_once::hf833e7144973d4be
at /rustc/a8314ef7d0ec7b75c336af2c9857bfaf43002bfc/library/core/src/ops/function.rs:280:13
33: 0x55b400d9f12e - std::panicking::try::do_call::h79761d203bfb6b46
at /rustc/a8314ef7d0ec7b75c336af2c9857bfaf43002bfc/library/std/src/panicking.rs:492:40
34: 0x55b400d9f12e - std::panicking::try::h0561cbbe1722251d
at /rustc/a8314ef7d0ec7b75c336af2c9857bfaf43002bfc/library/std/src/panicking.rs:456:19
35: 0x55b400d9f12e - std::panic::catch_unwind::hbca347ddd031b141
at /rustc/a8314ef7d0ec7b75c336af2c9857bfaf43002bfc/library/std/src/panic.rs:137:14
36: 0x55b400d9f12e - std::rt::lang_start_internal::{{closure}}::h0492050ad281ec32
at /rustc/a8314ef7d0ec7b75c336af2c9857bfaf43002bfc/library/std/src/rt.rs:128:48
37: 0x55b400d9f12e - std::panicking::try::do_call::h3ebce69871996bb3
at /rustc/a8314ef7d0ec7b75c336af2c9857bfaf43002bfc/library/std/src/panicking.rs:492:40
38: 0x55b400d9f12e - std::panicking::try::hbed537d20e728475
at /rustc/a8314ef7d0ec7b75c336af2c9857bfaf43002bfc/library/std/src/panicking.rs:456:19
39: 0x55b400d9f12e - std::panic::catch_unwind::h4185e2024c6a5d05
at /rustc/a8314ef7d0ec7b75c336af2c9857bfaf43002bfc/library/std/src/panic.rs:137:14
40: 0x55b400d9f12e - std::rt::lang_start_internal::h1899cfd715ca6829
at /rustc/a8314ef7d0ec7b75c336af2c9857bfaf43002bfc/library/std/src/rt.rs:128:20
41: 0x55b400ba9ad2 - main
42: 0x7fd85b1afb4a - __libc_start_call_main
43: 0x7fd85b1afc0b - __libc_start_main@@GLIBC_2.34
44: 0x55b400ba0985 - _start
45: 0x0 - <unknown>
make[1]: *** [rust/Makefile:303: rust/bindings/bindings_generated.rs] Error 1
make[1]: *** Deleting file 'rust/bindings/bindings_generated.rs'
make: *** [Makefile:1292: prepare] Error 2

Writing an LKM

Calling Rust functions via System Calls

Conclusion

While the use of Rust in Linux is still in its early stages, I was hoping it would be more accessible, so it was a bit disappointing. I found some articles that create kernel modules, but it seems that they cannot be used as-is due to differences in the environment. Many of them use forks of the Linux kernel such as https://github.com/Rust-for-Linux/linux or https://github.com/jackos/linux. Additionally, creating an LKM as I had initially planned seems to have a different process from creating one in C, making it difficult to create one easily. As this is still an area of significant change, it seems that continuous catching up will be necessary.

As for Rust as a language itself, I think it's the number one difficult language I want to use, so I want to gradually become proficient in it while tinkering with the kernel. It might be good to start with RedoxOS?

References

Steam Deck が発売されたので中身を見ていく‼

· 15 min read

steamdeck

はじめに

Steam Deck は Valve が開発したゲーム機のふりをした Linux マシンで Arch Linux をベースにした SteamOS を搭載しています.

Arch ユーザとしてはさわらずにはいられないなのでこの記事ではデスクトップモードにして Steam Deck の中身を見ていきます.

デスクトップモード

Steam Deck を起動するとゲーム用のモードで起動するので,まずは KDE のデスクトップに入る必要があります.

電源ボタンを長押しするとこのようなメニューが出てくるので,Switch to Desktop を選択します.

これで KDE のデスクトップに切り替わります!

Konsole がインストールされていたので,早速 SteamOS のインストールされた状態を見てみましょう.

ログインユーザの状態(などなど)

ログインユーザ名は deck で id, groups の結果はこんな感じでした.いたって普通ですね.

(deck@steamdeck ~)$ id
uid=1000(deck) gid=1000(deck) groups=1000(deck),998(wheel)
(deck@steamdeck ~)$ groups
wheel deck

ちなみにシェルは bash ですが,エラーコードが出るなどプロンプトは見やすくなってます.

ただ,.bashrc と .bash_profile はまっさらでした.

#
# ~/.bashrc
#

# If not running interactively, don't do anything
[[ $- != *i* ]] && return
#  SPDX-License-Identifier: MIT
#
# Copyright © 2020 Collabora Ltd.
# Copyright © 2020 Valve Corporation.
#
# This file is part of steamos-image-recipes.
#
# steamos-image-recipes is free software; you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation; either version 2.1 of the License, or
# (at your option) any later version.

#
# ~/.bash_profile
#

[[ -f ~/.bashrc ]] && . ~/.bashrc

どこか別の場所でプロンプトの環境変数だけ設定しているみたいですね.

(deck@steamdeck ~)$ echo $PS
$PS1 $PS2 $PS4
(deck@steamdeck ~)$ echo $PS1
(\[\033[1;32m\]\u@\h\[\033[1;34m\] \W\[\033[0m\])\$
(deck@steamdeck ~)$ echo $PS2
>
(deck@steamdeck ~)$ echo $PS4
+

脱線ついでに,tty の切り替えをやってみましたが,画面が真っ暗になるだけでログインプロンプトは表示されませんでした.

プリインストールされているパッケージ

それでは次にインストールされているパッケージを確認しようと思います.

Arch ベースなので sudo pacman -Q を実行したいのですが,その前にパスワードの設定をします.

passwd

パッケージリストはそこそこ多かったので gists に入れておきます.

SteamOS_preinstalled_pkgs.log

なんとなく目についたのはこのあたり.

  • AMD のチップだから amd-ucode が入ってる
  • btrfs-progs が入ってるけど Btrfs なの?
  • holo-* と steam* 関連は SteamOS 特有のなにか
  • holo-sudo は何ができるのか?
  • networkmanager は安定
  • openssh と openvpn がもともと入ってる
  • sshfs どこで使うんだろう?
  • なぜか zsh がすでに入ってる

AUR helper は yay が入っているようです.

(deck@steamdeck ~)$ yay -V
yay v10.3.0.r0.g4a93199 - libalpm v13.0.1

AUR からインストールされているパッケージはないみたい.「使いたいユーザは勝手に使えば?」みたいな感じなのかな….

ミラーリスト

ミラーリストは Steam Deck 用のサーバのみのようです.

(deck@steamdeck ~)$ cat /etc/pacman.d/mirrorlist
Server = https://steamdeck-packages.steamos.cloud/archlinux-mirror/$repo/os/$arch

Manjaro と同じで Arch のミラーよりもワンクッションおいて安定性を求める感じかなと妄想してみる.

ちなみにカーネルのバージョンは v5.13.0 のカスタムカーネルのようです.2022年10月3日なので2ヶ月位遅いのかな?(カスタムしたのが2ヶ月前なだけでベースバージョンはもっと前のやつかも)

(deck@steamdeck ~)$ uname -a
Linux steamdeck 5.13.0-valve21.3-1-neptune #1 SMP PREEMPT Mon, 03 Oct 2022 23:17:36 +0000 x86_64 GNU/Linux

Arch の現在のカーネルバージョンは v6.0.12 なので割と差が開いていますね.

mori@thinkpad-arch ~ % uname -a
Linux thinkpad-arch 6.0.12-arch1-1 #1 SMP PREEMPT_DYNAMIC Thu, 08 Dec 2022 11:03:38 +0000 x86_64 GNU/Linux

autostart, systemd

起動時に自動で実行されるアプリケーションを見てみます.ここで Steam Client が実行されているはず(多分).

(deck@steamdeck ~)$ ls -la .config/autostart/
total 8
drwxr-xr-x 2 deck deck 4096 Dec 17 17:34 .
drwxr-xr-x 17 deck deck 4096 Dec 17 20:57 ..
lrwxrwxrwx 1 deck deck 58 Dec 17 17:34 steamos-update-os-notifier.desktop -> /usr/share/applications/steamos-update-os-notifier.desktop

アップデートがあるときに通知するだけのやつみたいです.

[Desktop Entry]
Name=SteamOS Update Operating System Notifier
Comment=KUpdate Notifier for the new systemtray specification
Exec=/usr/lib/steamos/steamos-update-os-notifier
Icon=system-software-update
Type=Application
NoDisplay=true
OnlyShowIn=KDE
Keywords=kupdate-notifier;system;update;updater

じゃあ systemd にあるのかと思って systemctl list-units したけど特に見当たらず….というか user が実行しそうな気がするからそもそも systemd に登録するはずないか…?

Steam client の実行については今の所不明ですが,systemd のターゲットの中に cryptsetup を見つけました.

...
home-swapfile.swap loaded active active Swap
basic.target loaded active active Basic System
bluetooth.target loaded active active Bluetooth Support
cryptsetup.target loaded active active Local Encrypted Volumes
getty.target loaded active active Login Prompts
graphical.target loaded active active Graphical Interface
integritysetup.target loaded active active Local Integrity Protected Volumes
...

ディスク暗号化してるのかと思いましたが,パッケージリストにはそれらしきものはないので結局よくわかりませんでした.

dm-crypt であれば mkinitcpio のコンフィグになにかあるかと思いましたが,そもそも /etc/mkinitcpio.conf が存在しませんでした.

どうやって initramfs を作っているのか…?

ディスクとパーティション

接続されたデバイスは 512 GB の SSD のみでした.

なにやら複数のパーティションが作られていますね.

(deck@steamdeck ~)$ lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS
nvme0n1 259:0 0 476.9G 0 disk
├─nvme0n1p1 259:1 0 64M 0 part
├─nvme0n1p2 259:2 0 32M 0 part
├─nvme0n1p3 259:3 0 32M 0 part
├─nvme0n1p4 259:4 0 5G 0 part /
├─nvme0n1p5 259:5 0 5G 0 part
├─nvme0n1p6 259:6 0 256M 0 part /var
├─nvme0n1p7 259:7 0 256M 0 part
└─nvme0n1p8 259:8 0 466.3G 0 part /var/tmp
/var/log
/var/lib/systemd/coredump
/var/lib/flatpak
/var/lib/docker
/var/cache/pacman
/srv
/root
/opt
/home

fstab ファイルの中身を確認するとこんな感じでした.

(deck@steamdeck ~)$ cat /etc/fstab
# Static information about the filesystems.
# See fstab(5) for details.

# <file system> <dir> <type> <options> <dump> <pass>
# SteamOS partitions
#/dev/disk/by-partsets/self/rootfs / ext4 defaults 0 1
#/dev/disk/by-partsets/self/var /var ext4 defaults 0 2
/dev/disk/by-partsets/self/efi /efi vfat defaults,nofail,umask=0077,x-systemd.automount,x-systemd.idle-timeout=1min 0 2
/dev/disk/by-partsets/shared/esp /esp vfat defaults,nofail,umask=0077,x-systemd.automount,x-systemd.idle-timeout=1min 0 2
/dev/disk/by-partsets/shared/home /home ext4 defaults,nofail,x-systemd.growfs 0 2

とりあえず ext4 でフォーマットしてるのかな?

lsblk したときの nvme0n1p8 の表記が btrfs のサブボリュームの表記の仕方に似ていたので btrfs なのかと思ったけど違いました.

cf:

mori@thinkpad-arch ~ % lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS
nvme0n1 259:0 0 476.9G 0 disk
├─nvme0n1p1 259:1 0 100M 0 part /boot
├─nvme0n1p2 259:2 0 16M 0 part
├─nvme0n1p3 259:3 0 220.3G 0 part
├─nvme0n1p4 259:4 0 548M 0 part
└─nvme0n1p5 259:5 0 256G 0 part /var/log
/home
/.snapshots
/
mori@thinkpad-arch ~ % cat /etc/fstab
───────┬──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
│ File: /etc/fstab
───────┼──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
1 │ # Static information about the filesystems.
2 │ # See fstab(5) for details.
3 │
4 │ # <file system> <dir> <type> <options> <dump> <pass>
5 │ # /dev/nvme0n1p5
6 │ UUID=ed5e0803-dd6b-434e-8094-635498d65036 / btrfs rw,noatime,compress=lzo,ssd,space_cache=v2,subvolid=256,subvol=/@,subvol=@ 0 0
7 │
8 │ # /dev/nvme0n1p1
9 │ UUID=3C4A-3D26 /boot vfat rw,relatime,fmask=0022,dmask=0022,codepage=437,iocharset=iso8859-1,shortname=mixed,utf8,errors=remount-ro 0 2
10 │
11 │ # /dev/nvme0n1p5
12 │ UUID=ed5e0803-dd6b-434e-8094-635498d65036 /home btrfs rw,noatime,compress=lzo,ssd,space_cache=v2,subvolid=257,subvol=/@home,subvol=@home 0 0
13 │
14 │ # /dev/nvme0n1p5
15 │ UUID=ed5e0803-dd6b-434e-8094-635498d65036 /.snapshots btrfs rw,noatime,compress=lzo,ssd,space_cache=v2,subvolid=258,subvol=/@snapshots,subvol=@snapshots 0 0
16 │
17 │ # /dev/nvme0n1p5
18 │ UUID=ed5e0803-dd6b-434e-8094-635498d65036 /var/log btrfs rw,noatime,compress=lzo,ssd,space_cache=v2,subvolid=259,subvol=/@var_log,subvol=@var_log 0 0
19 │
───────┴──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────

Kernel

最後にカーネルのソースコードを見ようかと思ったのですが,/usr/src 下にコードはありませんでした….

パッケージの追加

pacman は使えそうなのでためしに neofetch をインストールしてみます.ロゴは Arch なのかSteamOS なのか….

まずはパッケージのデータベースを更新します.(jupiter と holo はArch では見たことがない名前ですね.)

(deck@steamdeck src)$ sudo pacman -Sy
[sudo] password for deck:
:: Synchronizing package databases...
jupiter is up to date
holo is up to date
core is up to date
extra is up to date
community is up to date
multilib is up to date

次に neofetch をインストールします.

...失敗しましたね.

(deck@steamdeck src)$ sudo pacman -S neofetch
resolving dependencies...
looking for conflicting packages...

Packages (1) neofetch-7.1.0-2

Total Installed Size: 0.33 MiB

:: Proceed with installation? [Y/n]
(1/1) checking keys in keyring [########################################################################] 100%
(1/1) checking package integrity [########################################################################] 100%
(1/1) loading package files [########################################################################] 100%
(1/1) checking for file conflicts [########################################################################] 100%
(1/1) checking available disk space [########################################################################] 100%
:: Processing package changes...
(1/1) installing neofetch [########################################################################] 100%
warning: warning given when extracting /usr/bin/neofetch (Can't create '/usr/bin/neofetch')
warning: warning given when extracting /usr/share/licenses/neofetch/ (Can't create '/usr/share/licenses/neofetch')
warning: warning given when extracting /usr/share/licenses/neofetch/LICENSE.md (Failed to create dir '/usr/share/licenses/neofetch')
warning: warning given when extracting /usr/share/man/man1/neofetch.1.gz (Can't create '/usr/share/man/man1/neofetch.1.gz')
Optional dependencies for neofetch
catimg: Display Images
chafa: Image to text support
feh: Wallpaper Display
imagemagick: Image cropping / Thumbnail creation / Take a screenshot
jp2a: Display Images
libcaca: Display Images
nitrogen: Wallpaper Display
w3m: Display Images
xdotool: See https://github.com/dylanaraps/neofetch/wiki/Images-in-the-terminal [installed]
xorg-xdpyinfo: Resolution detection (Single Monitor) [installed]
xorg-xprop: Desktop Environment and Window Manager [installed]
xorg-xrandr: Resolution detection (Multi Monitor + Refresh rates) [installed]
xorg-xwininfo: See https://github.com/dylanaraps/neofetch/wiki/Images-in-the-terminal [installed]
:: Running post-transaction hooks...
(1/1) Arming ConditionNeedsUpdate...
touch: setting times of '/usr': Read-only file system
error: command failed to execute correctly

ファイルを持ってくることはできたけど /usr 以下に書き込めなかったという感じでしょうか.

しかし,ls -l / した限りでは root なら書き込めそうです.

(deck@steamdeck src)$ ls -l /
total 71
lrwxrwxrwx 1 root root 7 Apr 29 2022 bin -> usr/bin
drwxr-xr-x 1 root root 136 Dec 14 08:40 boot
drwxr-xr-x 21 root root 4120 Dec 17 21:49 dev
drwx------ 4 root root 16384 Jan 1 1970 efi
drwx------ 4 root root 16384 Jan 1 1970 esp
drwxr-xr-x 1 root root 1024 Dec 17 23:24 etc
drwxr-xr-x 5 root root 4096 Dec 17 17:34 home
lrwxrwxrwx 1 root root 7 Apr 29 2022 lib -> usr/lib
lrwxrwxrwx 1 root root 7 Apr 29 2022 lib64 -> usr/lib
lrwxrwxrwx 1 root root 7 Apr 29 2022 mnt -> var/mnt
drwxr-xr-x 2 root root 4096 Dec 17 17:34 opt
dr-xr-xr-x 332 root root 0 Dec 17 17:39 proc
drwxr-x--- 6 root root 4096 Dec 17 22:43 root
drwxr-xr-x 25 root root 600 Dec 17 20:28 run
lrwxrwxrwx 1 root root 7 Apr 29 2022 sbin -> usr/bin
drwxr-xr-x 4 root root 4096 Dec 17 17:34 srv
dr-xr-xr-x 12 root root 0 Dec 17 17:39 sys
drwxrwxrwt 15 root root 580 Dec 17 21:46 tmp
drwxr-xr-x 1 root root 80 Dec 17 23:23 usr
drwxr-xr-x 14 root root 1024 Dec 17 19:02 var

'/usr': Read-only file system とあるので,アクセス権限以前の問題でしょうか?

調べてみると他のユーザも同じ状況のようです.

How to solve the read only system /usr

Developper Mode にしても Read-only は消えないようですね.

Steam Deck Developer Mode does not turn off the read-only filesystem

ただし,上記2つの URL の先でも示されているのですが,解決策はあるようです.

公式による解決策:

What if I want to do more than what’s available by flatpak?

Totally fine, though it comes with several caveats. Make sure you know what you’re doing and be careful about running random commands / scripts you find on the internet - you may get your Steam Deck into a bad state or compromise your data. In addition, anything you install outside of flatpak (via pacman for instance) may be wiped with the next SteamOS update.

With that out of the way, if you are going outside flatpak and need to edit the read-only image, you can enable that with the following command:

sudo steamos-readonly disable

See below for instructions on using sudo with Steam Deck. One more warning to complete the warning sandwich – don’t do the above unless you know what you’re doing.

そのとおりにやってみたところうまく行きました.

(deck@steamdeck src)$ sudo steamos-readonly disable
[sudo] password for deck:
(deck@steamdeck src)$ sudo pacman -S neofetch
resolving dependencies...
looking for conflicting packages...

Packages (1) neofetch-7.1.0-2

Total Installed Size: 0.33 MiB

:: Proceed with installation? [Y/n]
(1/1) checking keys in keyring [########################################################################] 100%
(1/1) checking package integrity [########################################################################] 100%
(1/1) loading package files [########################################################################] 100%
(1/1) checking for file conflicts [########################################################################] 100%
(1/1) checking available disk space [########################################################################] 100%
:: Processing package changes...
(1/1) installing neofetch [########################################################################] 100%
Optional dependencies for neofetch
catimg: Display Images
chafa: Image to text support
feh: Wallpaper Display
imagemagick: Image cropping / Thumbnail creation / Take a screenshot
jp2a: Display Images
libcaca: Display Images
nitrogen: Wallpaper Display
w3m: Display Images
xdotool: See https://github.com/dylanaraps/neofetch/wiki/Images-in-the-terminal [installed]
xorg-xdpyinfo: Resolution detection (Single Monitor) [installed]
xorg-xprop: Desktop Environment and Window Manager [installed]
xorg-xrandr: Resolution detection (Multi Monitor + Refresh rates) [installed]
xorg-xwininfo: See https://github.com/dylanaraps/neofetch/wiki/Images-in-the-terminal [installed]
:: Running post-transaction hooks...
(1/1) Arming ConditionNeedsUpdate...

この方法でインストールしたパッケージは SteamOS のアプデートで消える可能性があるようなので Flatpak を使ったほうが良いみたいですが,とりあえずこれで何でもできますね.

neofetch の実行結果は冒頭の通りでした.Steam のロゴマークでしたね.

おわり

Steam Deck は(一応)一般向けのゲーム機という位置づけなのでシステムが壊れないようにある程度の制限はあるようです.

また,全体的にディレクトリ構造がデフォルトとは違う,パーティションなどが複雑に分割されている,mkinitcpio などあるはずのものがないなど,普段使っている Arch の環境と違うことが多かったです.

Linux の勉強も兼ねてまた今度じっくり中身を見てみようと思います.

[Solved] paru, AUR helper didn't work due to openssl version error (November 7, 2022)

· One min read

Issue

I couldn't upgrade application via paru this morning. I got the error message as follows.

$ paru -Syu
paru: error while loading shared libraries: libssl.so.1.1: cannot open shared object file: No such file or directory

The problem was that the version of openssl had been updated from 1.1 to 3 and the version of the library that paru was using was gone.

Solution

I installed openssl v1.1 via pacman. Fortunately pacman was available this time.

sudo pacman -S openssl-1.1

[Solved] Arch Linux will not boot after upgrading GRUB (August 27, 2022)

· One min read

Issue

After upgrading grub, Arch Linux will not boot. All I get is "Welcome to GRUB!" message and no boot menu.

Systems

  • GRUB version
    • 2:2.06.r322.gd9b4638c5-1
  • My machine is dual boot and the partition layout is as follows:
nvme0n1     259:0    0 476.9G  0 disk
├─nvme0n1p1 259:1 0 100M 0 part <- EFI partition
├─nvme0n1p2 259:2 0 16M 0 part
├─nvme0n1p3 259:3 0 220.3G 0 part <- Windows C drive
├─nvme0n1p4 259:4 0 548M 0 part
└─nvme0n1p5 259:5 0 256G 0 part <- Linux filesystem

Solution

To solve the problem, I reinstalled GRUB with the following.

  1. Boot from Arch ISO (USB)
  2. Mount partitions
mkdir /mnt/boot
mount /dev/nvme0n1p5 /mnt
mount /dev/nvme0n1p1 /mnt/boot
  1. chroot
arch-chroot /mnt
  1. Reinstall GRUB
grub-install --target=x86_64-efi --efi-directory=/boot --bootloader-id=GRUB
  1. Update config
grub-mkconfig -o /boot/grub/grub.cfg
  1. exit, unmount and reboot
exit
umount /mnt/boot
umount /mnt
reboot