WSL2 上の ArchWSL で音を鳴らす

折角歯車も回ったことだし、音も鳴らしてみることにしよう。

PulseAudio ライブラリのインストール

リモートサーバで音を鳴らすので、ライブラリだけをインストール。wine で音を鳴らしたい場合は lib32-libpulse が必要かも知れない。

sudo pacman -S libpulse

ユーザ設定ディレクトリを作成して設定スケルトンをコピーする。

mkdir ~/.pulse
cp /etc/pulse/client.conf ~/.pulse/

設定ファイルを開き、デフォルトサーバを設定する。

vim ~/.pulse/client.conf

編集するところはここ。

...
default-server = <<ホストのマシン名>>.mshome.net
...

PulseAudio サーバのインストール

ホストに PulseAudio サーバをインストールする。入手可能なウェブサイトはいくつかある。ここでは、C:\wsl\pulseaudio\{bin,etc\pulse} に配置することにする。

  • 公式

www.freedesktop.org

mingw32 向け spec が付いているので、自分でビルドすると精神衛生上良いかもしれない。

  • Cendio ThinkLinc

https://www.cendio.com/pulseaudio

クライアントパッケージに同梱されている。

  • Open e-Goverment

https://osdn.net/projects/sfnet_open-egov/

MingW/MSYS とのこと。

PulseAudio サーバのセットアップ

C:\wsl\pulseaudio\etc\pulse\daemon.conf で、共有メモリ無効(エラーで使えない)、待機時間無限(接続しないでいると勝手に落ちる)を設定する。

enable-sm = no
exit-idle-time = -1

PulseAudio サーバ起動時にデフォルトスクリプトが読み込まれて各種 PulseAudio モジュールをロードするのだが、$Env:UserProfile\.config\pulse\default.pa になっているらしい。

mkdir $Env:UserProfile\.config\pulse\
ni $Env:UserProfile\.config\pulse\default.pa
notepad $Env:UserProfile\.config\pulse\default.pa

サウンド出力と、TCP 入力を設定する。

load-module module-waveout sink_name=output source_name=input record=0
load-module module-native-protocol-tcp auth-cookie-enabled=false auth-ip-acl=<<WSL2 のアドレス空間>>

遅延が気になるようであれば、module-waveout モジュールのオプションである fragments (デフォルト 12 (個)) や fragment_size (デフォルト 1024 (bytes)) を調整すると良い。

PulseAudio サーバの起動

以下のコマンドで PulseAudio サーバを起動する。

.\pulseaudio.exe -D

これだと PulseAudio がコンソールセッションを終了時まで奪ってしまうので、start (Start-Process コマンドレット) を使うとそのまま手順を続けられる。

start .\pulseaudio.exe -D

ArchWSL で何か音を鳴らす

音を鳴らすアプリは色々あるが、libpulse には paplay という WAV ファイルを読み込んで音を鳴らすだけのテストコマンドがあるので、これで慣らすのがお手軽だ。

しかし、単純に音を鳴らすだけではさみしいので、DeaDBeeF で音楽を聴いている体にしたい。

まずは、DeaDBeeF と依存ライブラリをインストールする。

sudo pacman -S deadbeef libmad gtk3

DeaDBeeF を起動し、Edit > Preference > Sound で Output plugin を PulseAudio output plugin にする。

f:id:anon_193:20200601222518p:plain

これで、音が鳴るようになる。 後は適当にカスタマイズして音楽を視聴する。

f:id:anon_193:20200601222040p:plain

PulseAudio サーバの終了

pactlpacmd で終了する。タスクマネージャなどでプロセスを終了させても特に問題ないようだ。

WSL2 上の ArchWSL で歯車を回す

Windows 10 May 2020 Update がリリースされて、マイ PC でも WSL2 が使えるようになった。おめでたい🎉

折角なので、WSL2 で ArchWSL をセットアップし、OpenGL デモでお馴染みの glxgears で歯車を回していく。

前提条件

  • Windows 10 May 2020 Update にアップグレードしている
  • PowerShell 7.0 をインストールしている
  • Chocolatey をインストールしている

WSL2 を使えるようにする

管理者モード PowerShell で Micorosoft テクニカルマニュアル ( https://docs.microsoft.com/ja-jp/windows/wsl/install-win10 ) に載っている手順を実行する。 wsl コマンドはデフォルト WSL バージョンを 2 にするもので、これは省いても良い。

dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart
dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart
wsl --set-default-version 2
Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Windows-Subsystem-Linux

WSL を一度も使ったことがないなら、最後のコマンドで再起動を求められるため再起動する。

また、Windows 10 May 2020 Update 淹れ立てだと WSL2 対応カーネルがインストールされていないのでインストールする。

docs.microsoft.com

ArchWSL をインストール

少しのことにも、先達はあらまほしき事なり、と徒然草にも書かれている。 恥ずかしい失敗をしないためにも、偉大な先達を参考に進めていく。

qiita.com

yuk7 版 ArchWSL GitHub リポジトリの release から zip をダウンロードして、適当な所に展開する。ここでは C:\wsl\arch とする。

github.com

とりあえず Arch.exe を起動すると、インストールプロセスが開始される。

インストールが完了したら、Arch.exe を起動してユーザを作成する。 mkdir は素 useradd してしまったからだが、賢明な皆様は useradd で作らせた方が良い。

useradd sakra
passwd sakra
mkdir /home/sakra

いったん ArchWSL から出て、管理者モード PowerShell でデフォルトログインユーザをさっき作成したユーザにする。

.\Arch.exe config --default-user sakra

ArchWSL に入り直して、sudoers に入れる。

EDITOR=vim visudo

pacman のミラーリポジトリを有効化する。あとで wine を使いたい人は pacman.conf で multilib リポジトリコメントアウトを外すように。

# ミラーリポジトリ
sudo vim /etc/pacman.d/mirrorlist
# リポジトリとか
sudo vim /etc/pacman.conf

pacman キーリングをセットアップする。

# 初期化
sudo pacman-key --init
# インストール
sudo pacman-key --populate
# 更新
sudo pacman -Syy archlinux-keyring

最低限必要なパッケージをインストールする。

sudo pacman -Syyuu --needed base-devel git go progress python mesa-demos xorg-xauth

Yay をインストールする。

git clone https://aur.archlinux.org/yay-git.git
sudo sed -i -e /exit\ \$E_ROOT/d /bin/makepkg
cd yay-git
makepkg
sudo pacman -U yay-git-*.pkg.tar.*

ArchWSL から出て、管理者モード PowerShell で ArchWSL を WSL2 に変換する。

wsl --set-version Arch 2

結構時間が掛かるのでコーヒーでも淹れて待つと良い。

Vcxsrv をインストール

管理者モード PowerShell で Chocolatey で vcxsrv をインストールする。

choco install vcxsrv

Vcxsrv を起動し、初回設定を行う。ほとんどは任意の設定で良いが、最後の Extra Settings で必ず Native opengl を無効にする(重要)。これをせずに進めると、あとで一向に動かない歯車を見ることになる。

f:id:anon_193:20200530232818p:plain

初期設定が完了したら、Windows ファイアウォールのプロンプトが表示される。強化された Windows ファイアウォールは WSL2 用バーチャルインタフェースをパブリックネットワークとみなすため、パブリックネットワークを許可する。

f:id:anon_193:20200530221444p:plain

見逃したら、強化された Windows ファイアウォールを起動し、パブリックネットワーク規則を拒否から許可に変更する。

セキュリティ確保のため、何れのケースでもスコープのリモート IP を WSL2 が使っているアドレス空間に制限することを強く推奨する。

X トークン認証を設定する

ユーザーモード PowerShell で Vcxsrv インストールディレクトリに入って、信頼されたトークンを発行する。

$Env:DISPLAY=localhost:0
.\xauth.exe generate <ローカルマシンのコンピュータ名>.mshome.net:0 . trusted
.\xauth.exe list

表示されたトークンを ArchWSL のユーザに登録する。

xauth add <トークン>

glxgears を起動する

DISPLAY 環境変数を設定し、満を持して glxgears を起動する。

export DISPLAY=_gateway:0
glxgears

f:id:anon_193:20200530233958p:plain

恣意的なタイミングでのパスワード変更を求めるべきではないし、変な生成ルールを課すべきでもないらしい

パスワード強度について本気出して考えてみようとしたけどやめた - めらんこーど地階という日記をはるか昔に書いたわけだけど、ここ最近になって、アメリカの政府機関が認証に関するガイドラインのドラフトを発行したという話を聞いて少し読んでみた。

DRAFT NIST Special Publication 800-63B Digital Authentication Guideline

パスワードに関係するのは「5.1.1. Memorized Secrets」の部分。
パスワードの恣意的なタイミングでの変更を求めるな (SHOULD NOT) とか、ときたま日本で必要だ、いや必要じゃないと紛糾している条件や、複数の文字種でパスワードを構成することを強制するな (SHOULD NOT) という画期的な条件があるけど、色々な人がいうに、パスワードの定期変更とか、そもそも憶えられないとかで、結局のところ分かりやすいパスワードを作ってしまう嫌いがあるから、だとか。
割られていることがハッキリしていない限りパスワードの付け替えを強制しないとか、割られやすいパスワードとか、サービス名を含んでいるとか、そう言うアホらしいのを弾くくらいにしておこうというのは、昨今のサービス氾濫時代からすれば、人間そんなに憶えらんないし、妥当なんでしょう。
ていうか、パスワード管理ソフトウェアとかサービスを使おう。

PowerShell 3.0からはじめるTakeWhile

前回、繰り返し構文とbreak文によるtakeを考えたけど、これには欠点がある。

具体的には、breakで大域脱出可能な繰り返し構文が上流にない場合、この戦略は破綻してしまう。

PS> $x = 1..10 | take 5
PS> $x
(何も返らない)

この有様だ。ラッパーを噛ませないと使えないなんて汎用的じゃない。

Select-Object -First の謎

Select-Object には -First というオプションが存在し、これはコマンドレットの中で唯一無限リストを打ち切ることが出来る。はて、ネイティブコマンドがどうやって打ち切っているのだろうか?

灯台もと暗し。答えはまさかの Microsoft Connect にあった。

In PSv3, the pipeline can be stopped. Select-Object supports this with its -First parameter by raising a (non-public) StopUpstreamCommandsException exception.

It is of great value to be able to stop the pipeline if your mission is completed before the emitting cmdlet has provided all results.

Microsoft Connect is Retired - Collaborate | Microsoft Docs

non-public exception の StopUpstreamCommandsException を raise することで列挙完了前にパイプラインを止めることが出来る、と質問者は主張している。名前からして break で使われている FlowControlException 派生であることは容易に想像出来るだろう。

Take-While の実装

さて、パイプラインを打ち切れる素敵な non-public exception をどうやって外部から raise するのか? 答えは簡単、System.Reflectionだ。

using System;
using System.ComponentModel;
using System.Collections.ObjectModel;
using System.Management.Automation;
using System.Reflection;

namespace Anon193.PowerShell.Toys
{
	[RunInstaller(true)]
	public class ToysSnapIn : PSSnapIn
	{
		public override string Name
		{
			get
			{
				return "Anon193.PowerShell.Toys";
			}
		}

		public override string Vendor
		{
			get
			{
				return "anon_193";
			}
		}

		public override string Description
		{
			get
			{
				return "N/A";
			}
		}

		public ToysSnapIn() : base()
		{
		}
	}
}

namespace Anon193.PowerShell.Toys.Commands
{
	[Cmdlet(VerbsCommon.Select, "While")]
	public class SelectWhileCommand : Cmdlet
	{
		private SessionState predicateSessionState;
		private ConstructorInfo stopExceptionCtor;

		[Parameter(Mandatory = true, Position = 1)]
		public ScriptBlock Predicate
		{
			get; set;
		}

		[Parameter(Mandatory = true, ValueFromPipeline = true)]
		public PSObject InputObject
		{
			get; set;
		}

		public SelectWhileCommand() : base()
		{
		}

		protected override void BeginProcessing()
		{
			var sessionStateProperty = typeof(ScriptBlock).GetProperty("SessionState", BindingFlags.NonPublic | BindingFlags.Instance);
			predicateSessionState = (SessionState)sessionStateProperty.GetValue(Predicate, null);

			var asm = Assembly.Load("System.Management.Automation");
			var stopExceptionType = asm.GetType("System.Management.Automation.StopUpstreamCommandsException");
			stopExceptionCtor = stopExceptionType.GetConstructors()[0];

			base.BeginProcessing();
		}

		protected override void ProcessRecord()
		{
			var oldDollarUnderbar = predicateSessionState.PSVariable.GetValue("_");
			var oldInput = predicateSessionState.PSVariable.GetValue("input");
			predicateSessionState.PSVariable.Set("_", InputObject);
			predicateSessionState.PSVariable.Set("input", InputObject);
			try
			{
				var results = predicateSessionState.InvokeCommand.InvokeScript(predicateSessionState, this.Predicate, null);
				if (results.Count > 0)
				{
					PSObject resultHead = results[0];
					if (resultHead.ImmediateBaseObject is bool && resultHead.Equals(false))
					{
						throw (Exception)stopExceptionCtor.Invoke(new object[]{ this });
					}
				}
			}
			catch (Exception e)
			{
				throw e;
			}
			finally
			{
				predicateSessionState.PSVariable.Set("_", oldDollarUnderbar);
				predicateSessionState.PSVariable.Set("input", oldInput);
			}
			this.WriteObject(InputObject);
		}
	}
}

これを library としてコンパイルし ( System.Manmagement.Automation の参照を忘れないこと! ) 、次のような psd1 ファイルをしつらえる。

@{
# コンパイル出力先に応じて変える
RootModule = 'Anon193Toys.dll'
ModuleVersion = '1.0'
# 以下お好みで
PowerShellVersion = '3.0'
GUID = '384c96a0-4ece-490b-8f0b-45e69258059c'
}

出来上がった dll ファイルと psd1 ファイルを PowerShell ホームディレクトリ ($PSHOME) の Modules サブディレクトリにサブサブディレクトリを mkdir して配置し、Import-Module すれば、Select-While と名付けられた TakeWhile が使えるようになる。

PS > Import-Module Anon193Toys
PS > function f(){ $x = 0; while ($true) {[System.Math]::Sin([System.Math]::PI * $x++ / 100);}}
PS > $r = &f | Select-While { $_ -lt 1 }

PS > $r
0
0.0314107590781283
0.0627905195293134
0.0941083133185143
0.125333233564304
(snip)
0.982287250728689
0.987688340595138
0.992114701314478
0.99556196460308
0.998026728428272
0.999506560365732
PS >

これは Enumerator でも 繰り返し構文でも上流を強制決済出来るので、煩わしいラッパーを使う必要がない。

唯一かつもっとも重い問題は、内部 Exception を Reflection で無理矢理引き出して Raise していることだ。今後の安定性は保証出来ない。利用は研究目的にとどめたいと思う。

PowerShell 3.0からはじめるunfoldr and take

最近 Haskell とかの関数型言語のリスト操作を PowerShell 2.0 で使うのにハマっている。
1つの例を挙げると、Haskell のリスト操作関数に Data.List#unfoldr というのがあって、これは unfoldr に与えた関数とシード値を使い、先頭から末尾の向きにリストを生成してくれる。ここで、unfoldr に与える関数は現在のシード値を受け取り、リストの要素とする値と次のシード値のtupleを返す。

The unfoldr function is a `dual' to foldr: while foldr reduces a list to a summary value, unfoldr builds a list from a seed value. The function takes the element and returns Nothing if it is done producing the list or returns Just (a,b), in which case, a is a prepended to the list and b is used as the next element in a recursive call.

Data.List

これが割と便利で、例えば配列を任意の位置で2分割する関数(HaskellではsplitAtとして提供されている)と組み合わせて、配列を任意の要素数ごとに分割したリストを生成したり、要素値が或る式に従う無限リストを得たりすることが出来る。

PowerShell に unfoldr 相当の組み込みコマンドレットはないので、自分で実装する必要がある。PowerShell には末尾再帰最適化がないので、ここでは繰り返し構造に変更している。

function unfoldr([scriptblock] $f, $b) {
    while($true) {
        $it = &$f($b)
        if ($it -eq $null) {
            # f b == Nothing
            break
        } else {
            # f b == Just (a,b)
            ,$a, $b = $it
            ,$a
        }
    }
}

1から10までの整数値のリストを生成するワンライナーは次のようになる。

PS> unfoldr {param($x) if ($x -eq 11) { $null } else { ,$x,($x+1) }} 1
1
(snip)
10

有限リストならこれで良いけど、無限リストの場合は途中で評価を打ち切らねば制御が戻ってこない。かと言って unfoldr に与える関数に打ち切り条件を込めるのも格好悪い。

そこで登場するのが take 関数。リストから指定した数の要素だけを取り出し、そこで評価を打ち切ってくれる。
これも PowerShell に存在しないので実装する必要があるのだけど、PowerShell 2.0 では unfoldr のループを、unfoldr に与えた関数以外で正しく止めることは出来ない。

filter take([int]$n) {
  if ($n -gt 0) {
    # これは書けるけど
    $n--
    $_
  } else {
    # 止めるコードが書けない
  }
}

Web 上では昔から break や PipelineStoppedException 例外を使ったコードが散見されるけど、絶対にやってはいけない。

filter take_using_break([int]$n) {
  if ($n -gt 0) {
    $n--
    $_
  } else {
    # ダメ
    break
  }
}

filter take_using_exception([int]$n) {
  if ($n -gt 0) {
    $n--
    $_
  } else {
    # これもダメ
    throw (New-Object System.Management.Automation.PipelineStoppedException)
  }
}

前者はそこでスクリプトが終了してしまうし(trapしてると変なコードフローになった上に終了する)、後者はパイプラインの評価値が真っ白になる。

PS> $x = unfoldr {param($x) ,$x,$x } 1 | take_using_break 10; $x
(何も返らない)
PS> trap {continue}; $y = unfoldr {param($x) ,$x,$x } 1 | take_using_exception 10; $y
(何も返らない)

条件を関数として与えることが出来る takeWhile 関数と言うのもあるけど、実装しても同じ目に遭うことだろう。

それではどうすれば打ち切りを実現出来るのか。答えは簡単で、PowerShell 2.0 を 3.0 にバージョンアップするだけでいい。驚くべきことに、これだけで take_using_break がそのまま使える。

PS> $x = unfoldr {param($x) ,$x,$x } 1 | take_using_break 10; $x
1
(snip)
1
PS>

ラクリは PowerShellのパイプライン後段にある break の取り扱い方。2.0 では挙動不審に陥るけど、3.0 では、後段にある break が、直近の上流の繰り返し構造への break と見なされ、大域脱出することが出来るようになっている(らしい)。
# どこにもそのようなことを扱っている記事が見当たらないので、正式な意味論は不明

何はともあれ take が使えるようになって、私の PowerShell ライフはますます充実しそうだ。

HP ML110 G7でNexentaStorを飼い始めた

少し前にHP ML110 G7が9980円くらいで買える激安セールがあるとtwitterづてで知って一台ポチ。
そろそろポンコツファイルサーバとおさらばしたかったというのがあって、ファイルサーバとして仕立てることにした。
追加で購入したパーツは HP ProLiant/ML110 G7 - wiki@nothing を参考にして、

  • SanMax DDR3-1333 CL9 ECC SMD-16G68EHP-13H-Q (4GB×4枚) × 1箱
  • WESTERN DIGITAL WD20EARX (3.5inch 2TB) × 4発

の2つ。
元々付いていたメモリとHDDをすべて取り外し、購入したモノを実装した。工具が要らないのは何気なく便利だったけど、HDDケースのピンが堅くて、コツを掴むまで結構苦戦してしまった。外したパーツのうち、HDDはシステムドライブとして使うために、SATA#2に移し替えて、5インチベイの適当なところに転がしておいた。本当はちゃんと固定した方が良いのだけど、手元に丁度良い弁当箱やケースがなかったので保留。
ファイルサーバ用のシステムは、例によってOpenBSDにしようかと考えていたのだけれど、OpenBSD環境はネットブックにもう一つ確保しているし、専用のシステムも触ってみたいということで、別のシステムを検討することに。
調べてみると、

あたりを見つけた。当座の使用目的はSambaなので、個人的にはSambaが速いのが良い。
これらシステムを一つ一つレビューしてみるのも面白いかも知れないけど、そこまで気長にやってられないので他人のレビューを見ると、どうもOpenSolarisのcifs serverがめっちゃはやいらしい。
そんなわけで、使うシステムはNexentaStorに決めた。
光メディアにセットアップイメージを焼き、SATA#2 HDDにNexentaStorをセットアップ。最初のストレージ認識で、SATA#1 HDDの一つが認識されなくて少し肝を冷やした。単に刺し方が甘かっただけらしく、セットアップ後にシャットダウンして刺しなおしたら正しく認識された。
適当な管理設定を行った後、zpoolにSATA#1のHDD4発を追加し、シングルパリティZFSを構成。zpool合計サイズが8GBで、ファイルシステムのサイズは5.5GB位。
最後にrsyncで旧ファイルサーバからデータを転送して、移行を無事完了した。
肝心のパフォーマンスだけど、このトピックを書いている時点でのCrystalDiskMarkは以下の通り。

大きな違いというと、ファイル列挙の速度が体感的に凄く改善された。メタデータの読み取り速度が改善されたのだろう。フォルダサイズを計上させると、秒間3000ファイル位の速さでスキャンしていく程度のパフォーマンス。
ただ、すこし気になるのは4K QD32(NCQ)のパフォーマンス。本当に、こんなに速くなるのか……?

OpenBSD 5.1 releaseでruby-buildからのruby 1.9.2-p320, 1.9.3-p194がinstallできない

railsアプリサーバを立ち上げるため、OpenBSD+rbenv+ruby-buildの環境を作る機会が得られた。
取りあえずOpenBSDをinstallして、専用のユーザを立てたのち、そこにrbenvとruby-buildをinstall。

# useradd -m myrails
# passwd myrails
...
# pkg_add git
...
# su - myrails
$ git clone https://github.com/sstephenson/rbenv.git .rbenv
...
$ mkdir -p .rbenv/plugins && cd .rbenv/plugins
$ git clone https://github.com/sstephenson/ruby-build.git
...
$ vi ~/.profile
...

あとはruby-buildでrubyをinstallするだけ……だったのだが。

rbenv install ruby 1.9.3-p194

bigdecimalのmake時に../../.ext/のbigdecimalディレクトリが生成されなくて死ぬ。

install: install-so install-rb

install-so: $(RUBYARCHDIR)
install-so: $(RUBYARCHDIR)/$(DLLIB)
install-rb: pre-install-rb install-rb-default
install-rb-default: pre-install-rb-default
pre-install-rb: Makefile
pre-install-rb-default: Makefile
pre-install-rb-default: $(RUBYLIBDIR)/bigdecimal
...
$(RUBYLIBDIR)/bigdecimal:
	$(Q) $(MAKEDIRS) $@

大体ここら辺のルールらしい。どうみても通るように見えるんですが。

rbenv install ruby 1.9.2-p320

inspect_peercredでucred.pid/uid/gidをアクセスするコードを書いていて、コンパイル時に死ぬ。

This is because we use "struct sockpeercred" instead of "struct
ucred": you need to patch option.c

Re: Compiling Ruby 1.9.2 on OpenBSD 5.1

MLにある出来たてのスレッドによるとどうもバグらしい。
ど、どうするんだ、これは……