ラベル Windows の投稿を表示しています。 すべての投稿を表示
ラベル Windows の投稿を表示しています。 すべての投稿を表示

2023-05-13

WSL shrink maximum image size

WSL がディスクを食い尽くすのをの何とかしたい。ディスク(vhdx)サイズが 1TB 決め打ちで変更できないとか、Microsoft のそういう所ほんと嫌い。

基本戦略は上記に従う。ちなみに老人なので PowerShell とかは知らない。

まず Ubuntu をクリーンインストールし、即終了。(初回インストール時は、wsl --install を管理者権限で実行する必要あり)

C:\Users\user>wsl --version
WSL バージョン: 1.2.5.0
カーネル バージョン: 5.15.90.1
WSLg バージョン: 1.0.51
MSRDC バージョン: 1.2.3770
Direct3D バージョン: 1.608.2-61064218
DXCore バージョン: 10.0.25131.1002-220531-1700.rs-onecore-base2-hyp
Windows バージョン: 10.0.22621.1555

C:\Users\user\Documents>wsl -l -v
Linux 用 Windows サブシステムにディストリビューションがインストールされていません。

'wsl.exe --list --online' を使用して利用可能なディストリビューション
を一覧表示し、'wsl.exe --install <Distro>' を使用してインストールします。

また、Microsoft Store にアクセスしてディストリビューションをインストールすることもできます:
https://aka.ms/wslstore
Error code: Wsl/WSL_E_DEFAULT_DISTRO_NOT_FOUND

C:\Users\user\Documents>wsl --install
インストール中: Ubuntu
Ubuntu がインストールされました。
Ubuntu を起動しています...
Installing, this may take a few minutes...
Please create a default UNIX user account. The username does not need to match your Windows username.
For more information visit: https://aka.ms/wslusers
Enter new UNIX username: user
New password:
Retype new password:
passwd: password updated successfully
この操作を正しく終了しました。
Installation successful!
To run a command as administrator (user "root"), use "sudo <command>".
See "man sudo_root" for details.

Welcome to Ubuntu 22.04.2 LTS (GNU/Linux 5.15.90.1-microsoft-standard-WSL2 x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/advantage


This message is shown once a day. To disable it please create the
/home/user/.hushlogin file.

user@MY-PC:~$
logout

C:\Users\user\Documents>wsl --shutdown

C:\Users\user\Documents>wsl -l -v
  NAME      STATE           VERSION
* Ubuntu    Stopped         2

Ubuntu の vhdx をコピー。ファイルの場所は臨機応変に探す。

C:\Users\user\Documents>copy "%LOCALAPPDATA%\Packages\CanonicalGroupLimited.Ubuntu_79rhkp1fndgsc\LocalState\ext4.vhdx" ubuntu.vhdx
        1 個のファイルをコピーしました。

新しい vhdx を作る(ここでは 10GB)。

C:\Users\user\Documents>diskpart

Microsoft DiskPart バージョン 10.0.22621.1

Copyright (C) Microsoft Corporation.
コンピューター: MY-PC

DISKPART> create vdisk file="C:\Users\user\Documents\ext4.vhdx" maximum=10000 type=expandable

  100% 完了しました

DiskPart により、仮想ディスク ファイルが正常に作成されました。

DISKPART> exit

Ubuntu を起動し、新旧 vhdx をアタッチ。

C:\Users\user\Documents>wsl
To run a command as administrator (user "root"), use "sudo <command>".
See "man sudo_root" for details.

Welcome to Ubuntu 22.04.2 LTS (GNU/Linux 5.15.90.1-microsoft-standard-WSL2 x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/advantage


This message is shown once a day. To disable it please create the
/home/user/.hushlogin file.

user@MY-PC:/mnt/c/Users/user/Documents$ wsl.exe --mount --vhd --bare ubuntu.vhdx
この操作を正しく終了しました。

user@MY-PC:/mnt/c/Users/user/Documents$ wsl.exe --mount --vhd --bare ext4.vhdx
この操作を正しく終了しました。

user@MY-PC:/mnt/c/Users/user/Documents$ lsblk
NAME MAJ:MIN RM   SIZE RO TYPE MOUNTPOINTS
sda    8:0    0 363.3M  1 disk
sdb    8:16   0     1G  0 disk [SWAP]
sdc    8:32   0     1T  0 disk /snap
                               /mnt/wslg/distro
                               /
sdd    8:48   0     1T  0 disk
sde    8:64   0   9.8G  0 disk

旧 vhdx が sdd、新 vhdx が sde だと分かるので、新 vhdx (/dev/sde)をフォーマット。

user@MY-PC:/mnt/c/Users/user/Documents$ sudo mkfs.ext4 /dev/sde
[sudo] password for user:
mke2fs 1.46.5 (30-Dec-2021)
Discarding device blocks: done
Creating filesystem with 2560000 4k blocks and 640848 inodes
Filesystem UUID: 0d215fd7-0556-4259-8854-54fa8768d86c
Superblock backups stored on blocks:
        32768, 98304, 163840, 229376, 294912, 819200, 884736, 1605632

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

新旧 vhdx をマウントし、中身をコピーし、Ubuntu を終了。

user@MY-PC:/mnt/c/Users/user/Documents$ sudo mkdir /mnt/vdisk-src /mnt/vdisk-dst

user@MY-PC:/mnt/c/Users/user/Documents$ sudo mount -r /dev/sdd /mnt/vdisk-src

user@MY-PC:/mnt/c/Users/user/Documents$ sudo mount /dev/sde /mnt/vdisk-dst

user@MY-PC:/mnt/c/Users/user/Documents$ ls /mnt/vdisk-src /mnt/vdisk-dst
/mnt/vdisk-dst:
lost+found

/mnt/vdisk-src:
bin   dev  home  lib    lib64   lost+found  mnt  proc  run   snap  sys  usr
boot  etc  init  lib32  libx32  media       opt  root  sbin  srv   tmp  var

user@MY-PC:/mnt/c/Users/user/Documents$ sudo cp -axT /mnt/vdisk-src /mnt/vdisk-dst

user@MY-PC:/mnt/c/Users/user/Documents$ ls /mnt/vdisk-dst
bin   dev  home  lib    lib64   lost+found  mnt  proc  run   snap  sys  usr
boot  etc  init  lib32  libx32  media       opt  root  sbin  srv   tmp  var

user@MY-PC:/mnt/c/Users/user/Documents$ wsl.exe --shutdown

Ubuntu の vhdx を新 vhdx にすり替え、Ubuntu を起動してディスクサイズを確認。

C:\Users\user\Documents>move ext4.vhdx "%LOCALAPPDATA%\Packages\CanonicalGroupLimited.Ubuntu_79rhkp1fndgsc\LocalState\ext4.vhdx"
C:\Users\user\AppData\Local\Packages\CanonicalGroupLimited.Ubuntu_79rhkp1fndgsc\LocalState\ext4.vhdx を上書きします か? (Yes/No/All): y
        1 個のファイルを移動しました。

C:\Users\user\Documents>wsl
To run a command as administrator (user "root"), use "sudo <command>".
See "man sudo_root" for details.

Welcome to Ubuntu 22.04.2 LTS (GNU/Linux 5.15.90.1-microsoft-standard-WSL2 x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/advantage


This message is shown once a day. To disable it please create the
/home/user/.hushlogin file.

user@MY-PC:/mnt/c/Users/user/Documents$ df -h | grep /dev/sd
/dev/sdc        9.6G  1.1G  8.0G  12% /

2025-06-09 追記

その後、WSL 2.0.5 で設定項目 vhdSize が追加された模様。

いやいや、設定ファイルじゃなくて、install 時に個別に指定したいんだよ。と思ったら、コマンドラインオプションからできるようになっていた。

C:\Users\user>wsl --version
WSL バージョン: 2.5.7.0
カーネル バージョン: 6.6.87.1-1
WSLg バージョン: 1.0.66
MSRDC バージョン: 1.2.6074
Direct3D バージョン: 1.611.1-81528511
DXCore バージョン: 10.0.26100.1-240331-1435.ge-release
Windows バージョン: 10.0.26100.4061

C:\Users\user>wsl --help
<...snip...>

    --install [Distro] [Options...]
        <...snip...>

        オプション:
            <...snip...>

            --vhd-size <MemoryString>
                ディストリビューションを保存するディスクのサイズを指定します。

他にも --location とか、ずっと欲しかったオプションも実装されていた。まともなリリースノートが見つからなかったので、いつからできるようになったかは不明。

2023-05-04

WSL install error 0x80370114

ある PC で、何度やっても WSL インストールが失敗する。

C:\Windows\System32>wsl --install
インストール中: Ubuntu
Ubuntu がインストールされました。
Ubuntu を起動しています...
Installing, this may take a few minutes...
WslRegisterDistribution failed with error: 0x80370114
Error: 0x80370114 ???????????????????????????????????

Press any key to continue...
ディストリビューション 'Ubuntu' のインストール プロセスが次の終了コードで失敗しました: 1。
Error code: Wsl/InstallDistro/WSL_E_INSTALL_PROCESS_FAILED

事前に Ubuntu をアンインストールしてもしなくても、同じエラーコードで失敗する。心当たりとしては、最初に管理者にならずに wsl --install を実行してしまったこと。当然インストールは失敗し、以降は管理者で実行しても失敗するようになった。

別の PC で wsl --install して成功した時の様子。こちらは Ubuntu インストール前に幾つかインストールが実行されている。

C:\WINDOWS\system32>wsl --install
インストール中: 仮想マシン プラットフォーム
仮想マシン プラットフォーム はインストールされました。
インストール中: Linux 用 Windows サブシステム
Linux 用 Windows サブシステム  はインストールされました。
インストール中: Linux 用 Windows サブシステム
Linux 用 Windows サブシステム  はインストールされました。
インストール中: Ubuntu
Ubuntu はインストールされました。
要求された操作は正常に終了しました。変更を有効にするには、システムを再起動する必要があります。

失敗する PC の方で OptionalFeatures.exe を確認すると、「仮想マシン プラットフォーム」「Linux 用 Windows サブシステム」のどちらも無効になっていた。一方、「設定 > アプリ > インストールされているアプリ」には「Linux 用 Windows サブシステム」が存在した。これをアンインストールして再度 wsl --intall を管理者で実行すると、ようやくインストールが成功した。

恐らく、「仮想マシン プラットフォーム」の有効化は管理者でなかったために失敗し、「Linux 用 Windows サブシステム」はユーザーアプリなのでインストールに成功していたのだろう。そして wsl --install は「Linux 用 Windows サブシステム」の存在だけをチェックしているのではないだろうか。管理者にならずに実行してしまうミスは普通にあり得るし、それで WSL がインストール不能になるというのは結構な重大バグなのでは。

2023-05-03

Lenovo X280 petit freeze

Lenovo X280 を使っていて、長らくプチフリーズに悩まされていた。大体は 1 秒程度、たまに酷い時は固まったままになる。そういう時は sleep & resume すると回復する。

フリーズといっても固まっているのは画面だけで、その他は正常に動いている。例えば動画視聴時に固まった場合でも、音声は流れ続けている。恐らくディスプレイの同期周りが上手くいっていないのだろうな、と心当たりを調べるも、ずっと解決できずに居た。

やはりグラフィックスドライバーか。当然疑ってはいたが、それらしい設定画面を見つけられなかったのだった。まさか右クリックメニューにしか導線がないとは。ただし最初はタスクトレイにあったかも知れない(覚えてない)。それを私が無効にした可能性は大いにある。

パネル・セルフリフレッシュを無効にすると、問題は完全に解消した。心なしかマウスポインターやスクロールまでも滑らかになったような気がする。フォーラムの投稿日は 2019-07、もっと早くにググっておけば良かった。

改めてパネル・セルフリフレッシュでググってみると、これの弊害は様々にあるようだ。現在困っている・いないに関わらず、こんなものは今すぐ無効にすべき。

2022-02-20

Emacs on Windows: Markdown command *** is not found

先日、markdown-mode が更新(v2.5)されていることに気が付いた。今使っているのが v2.3。あれ、v2.4 リリースには気付かなかったっけ?と思いながら v2.5 にして暫くした後、markdown コマンドが動かなくなっていることに気が付いた。

Markdown command markdown is not found

あー、なんか思い出してきた。昔 v2.4 にした時、やはり markdown コマンドが動かなくて v2.3 に戻したような気がする。v2.5 になっても直っていないということは、直す気がないか、私の環境がニッチなのだろう。このままずっと v2.3 というのも嫌なので、面倒だが調べてみる。

私の環境は MSYS2 + NTEmacs。M-! (shell-command)では markdown コマンドを実行できる。しかし markdown-mode からだと見つけられない。

      (when (and (stringp command) (not (executable-find command)))
        (user-error "Markdown command %s is not found" command))

上記が問題の部分。

(executable-find "markdown")
nil

(locate-file "markdown" exec-path)
"c:/home/user/bin/markdown"

これだ。私の場合、markdown コマンドは Ruby スクリプトだ。MSYS2 からは実行できても、実行ビットを解さない NTEmacs には単なるファイルにしか見えない。幸い、markdown-command にはリストも設定できるので、下記のようにして解決した。

(when (executable-find "env")
  (setq markdown-command `("env" ,markdown-command)))

以上、結果からいうと、やはり私の環境のせいだった。しかし NTEmacs のことを考慮すれば、executable-find のチェックはやり過ぎのように感じる。単に locate-file にするか、そもそもチェックする必要があるのか?という気もする。困っているのが私だけなら良いのだが。

2017-05-13

Excel: Open TBX file

よくドキュメントを書く人にとって、「カタカナ表記をどうするか?」というのは常に悩ましい問題だ。つまり、「サーバ」or「サーバー」?、「インタフェース」or「インターフェイス」?とかいうやつ。私は、以前は JTCA とかいう所の「外来語(カタカナ)表記ガイドライン」に従っていた。

しかしこれ、実際に使ってみると色々と美味くない。まず、

  • IT 用語の例が少ない。

一応、用語集は付いているものの、一般用語が大半を占めるため、IT 業界に居てこれで事足りることは絶対にない。更に、

  • Microsoft 用語と相違がある。

例えば、上記ガイドラインはで「レジストリー」、Windows はで「レジストリ」。画面キャプチャして手順書を作ったりすると、この不一致が気持ち悪くて仕方ない。Windows ユーザーが最も多いことは周知の事実なのだから、合わせるならむしろ Windows に合わせるべきだろう。

ということで、今では全て Microsoft 用語を使うようにしている。

ここでは用語検索もできるし、当然ながら IT 用語は網羅されている。ここで検索できない用語を使うということは、何か間違っている可能性すらある。

さて、上記の用語検索は便利だが、お世辞にも使い易いとは言えない。それどころか偶にエラーになったり、サイトにアクセスできない時すらある。なので、用語集をローカルにダウンロードして参照したい、となるのは当然の流れ。実際、ダウンロードは簡単にできる。しかしその拡張子は .tbx とかなってる。

一体これをどうしろと・・・? :-o

tbx でググってみても、要領を得ないサイトばかり。危うく得体の知れないソフトウェアをインストールさせられそうになったりする。そうじゃなくて、もっとこう、何かないの?

just add ".xml" to your file, open in Excel ...

なになに? 「拡張子を .xml にして Excel で開け」と。ふむ、何かダイアログが出てきたけど適当に OK して・・・、お、何か Excel がすごい頑張ってるっぽい(タスクマネージャーを見ながら)。そして待つこと数分。

Excel 先輩まじイケメン。 X-D

あとは不要な列を削除して .xlsx 形式で保存すれば、サイズもぐっとコンパクトになる。いやー、久しぶりに Excel 先輩の本気を見た。

2016-10-27

Windows batch: Multi-level variable expansion

この間 getopt.cmd を書いていて思った。変数展開した結果を変数展開するにはどうすればいいんだろう?、と。言葉にすると難しいが、要は C 言語でいうところの、ポインターのポインターから元データを参照する感じ。

call を使うのがいいようだ。call には変数展開後の文字列が引数として渡るので、どんなコマンドにも応用できる。call にこんな使い方があったとは。

test.bat:

@echo off
set hoge=HOGE
set r_hoge=hoge
set rr_hoge=r_hoge
echo %hoge%
call echo %%%r_hoge%%%               & rem run 'echo %hoge%'
call call echo %%%%%%%rr_hoge%%%%%%% & rem run 'call echo %%%r_hoge%%%'
>test.bat
HOGE
HOGE
HOGE

Windows バッチはこういう妙なところで懐が深かったりする。やろうと思えば大抵のことは(頑張れば)できる。でもどんな良い面があっても、圧倒的マイナス面で ks 扱いされる Windows バッチさん。

2016-10-23

Windows batch: Getopt

Windows バッチが ks なことは今さら言うまでもないが、最もお手軽な手段であることも事実。特にユーザーに配布して実行させたい場合、環境の差異を気にしないくて良いのは何にも勝る利点だ。同様の意味で WSH も素晴らしいが、結果が同じならよりシンプルな方を選びたい。

という訳で私はよくバッチも書くが、ちょっと凝ったバッチを書こうとしたとき、オプション処理にはいつも悩まされる。きちんとやろうとすると面倒だし、どうせならこの手の処理は共通化したい。誰か getopt みたいなやつ作ってねーのかよ、と偶にググってみても、これといったものが見つかった試しがない。

まあ、もしバッチでできたらとっくに誰か作ってるよねー、と今までは諦めていたのだが、今回ふと思って試してみた。

test.bat:

@echo off
setlocal
set opt=hoge
set opt_%opt%=HOGE
echo opt_hoge=%opt_hoge%
>test.bat
opt_hoge=HOGE

え!? set の左辺で変数展開できちゃうの?

そうか、set もコマンドだから、引数中の変数は展開されてから set に渡るのか。ていうか、これ使えば getopt できるんじゃね?

getopt.cmd:

@echo off

if not "%~1"=="/?" goto :main
echo.Usage:
echo.  call %~nx0 [/OPTION[:VALUE]]...
echo.
echo.Description:
echo.  Parse options and define 'OPT_^<OPTION^>' variables, and set 'OPTIND' as
echo.  the number of processed arguments.
echo.
echo.Exsample:
echo.  When a batch file is invoked with arguments,
echo.
echo.    ^>example.bat /foo /bar:BAR /baz:"B A Z" arg1 arg2 ...
echo.
echo.  In the batch file, a typical usage is:
echo.
echo.    call %~nx0 %%*
echo.    for /L %%%%i in (1,1,%%OPTIND%%) do shift /1
echo.
echo.  Then a boolean option '/foo' can be tested as below.
echo.
echo.    if defined OPT_FOO ^(
echo.      echo /foo was given
echo.    ^) else ^(
echo.      echo /foo was not given
echo.    ^)
echo.
echo.  Other options have a value can be used as regular variable.
echo.
echo.    echo bar=%%OPT_BAR%%
echo.    echo baz=%%OPT_BAZ%%
echo.
echo.  Remaining arguments are referred through batch parameter.
echo.
echo.    echo args=%%1 %%2 %%3 %%4 %%5 %%6 %%7 %%8 %%9
echo.
echo.  Note that '%%*' batch parameter is never updated by 'shift'.
exit /b 1

:main
set OPTIND=0

:_loop_args
  set _getopt_arg=%1
  if defined _getopt_arg (
    call :parse_arg %_getopt_arg%
    if not errorlevel 1 (
      shift /1
      set /a OPTIND+=1
      goto :_loop_args
    )
  )
set _getopt_arg=
set _getopt_opt=
set _getopt_value=
set _getopt_bool=
exit /b 0

:parse_arg
set _getopt_opt=
set _getopt_value=%~1
set _getopt_bool=1
if not "%_getopt_value:~0,1%"=="/" goto :_parse_arg_ret
set _getopt_value=%_getopt_value:~1%
:_loop_arg_chars
  if not defined _getopt_value goto :_parse_arg_ret
  if "%_getopt_value:~0,1%"==":" (
    set _getopt_bool=
    set _getopt_value=%_getopt_value:~1%
    goto :_parse_arg_ret
  ) else (
    set _getopt_opt=%_getopt_opt%%_getopt_value:~0,1%
    set _getopt_value=%_getopt_value:~1%
  )
  goto :_loop_arg_chars
:_parse_arg_ret
if not defined _getopt_opt exit /b 1
if defined _getopt_bool (
  call :set_opt %_getopt_opt% %_getopt_bool%
) else (
  call :set_opt %_getopt_opt% %_getopt_value%
)
exit /b 0

:set_opt
set OPT_%1=%~2
exit /b 0

できちゃった :-D。使い方は次の通り。

test_getopt.bat:

@echo off
setlocal

call getopt.cmd %*
for /L %%i in (1,1,%optind%) do shift /1

if defined opt_hoge echo /hoge was given
if not defined opt_hage echo /hage was not given
echo /piyo=%opt_piyo%
echo /fuga=%opt_fuga%
echo /boke=%opt_boke%
echo %1 %2 %3 %4 %5 %6 %7 %8 %9
>test_getopt.bat /hoge /piyo:PIYO /fuga:"FU GA" /boke: arg1 arg2 ...
/hoge was given
/hage was not given
/piyo=PIYO
/fuga=FU GA
/boke=
arg1 arg2 ...

まだ改良の余地はあると思うが、私が使うには当面これで十分。

ところで今回の件で、Windows バッチは変数名に記号(どころか空白も!)を使えることを知ったが、ならば getopt.cmd に /? を渡せば変数 opt_? を定義してくれそうに思う。しかしそれは叶わない。

>call getopt.cmd /?
バッチ プログラムを別のバッチ プログラムから呼び出します。

CALL [ドライブ:][パス]ファイル名 [バッチパラメーター]

  バッチパラメーター   バッチ プログラムで必要なコマンド ライン情報を指定します。

コマンド拡張機能を有効にすると、CALL は次のように変更されます:

<...snip...>

call が /? を奪い取る模様。やっぱ Windows バッチって ks だわ。

2016-10-11

ADODB.Stream: Read write-locked file

あるシステムのログ集計を、Excel マクロでやろうとしたときのこと。

ログは UTF-8 で書かれており、ググれば ADODB.Stream を使えば良いことは直ぐに分かる。ほどなくマクロは完成し、本番投入の直前、その問題は発覚した。

ADODB.Stream.LoadFromFile は、書き込みロックが掛かったファイルを読み込めない!

それはつまり、システムが開いている当日分のログを集計できないことを意味する。しかし、今回の要件としてそれは受け入れられない。何てこった。そんなこと Google 先生も教えてくれなかったよ。

Sub Test1()
    With CreateObject("ADODB.Stream")
        .Type = 2   ' 2=adTypeText
        .Charset = "UTF-8"
        .Open
        .LoadFromFile "WriteLocked.txt"
        ' ReadText...
        .Close
    End With
End Sub

ならば、アクセスモード(Stream.Mode)を読み取り専用(adModeRead)にすれば開けるはず・・・、

Sub Test2()
    With CreateObject("ADODB.Stream")
        .Mode = 1   ' 1=adModeRead
        .Type = 2   ' 2=adTypeText
        .Charset = "UTF-8"
        .Open
        .LoadFromFile "WriteLocked.txt"
        ' ReadText...
        .Close
    End With
End Sub

どういうことなの。

恐らく、アクセスモードは Stream に対してのものであり、Stream が読み取り専用となったために Stream への書き込みメソッドである LoadFromFile がブロックされたのだと思われる。そうすると LoadFromFile に対してアクセスモードを指定する術がない以上、この方向での解決策は絶望的に思える。

ではファイルの読み込みは別の手段で行い、それを Stream に食わせたらどうか?

じゃそれ FileSystemObject で! と思ったら、FileSystemObject はバイナリファイルを読み込めないときた。ダメ元で無理やり読み込ませてみたら、化け化けになった。泣きたい。

結局、最も基本的な方法で読み込ませることで上手くいった。

Sub Test3()
    Dim fd As Long, _
        buf() As Byte
    fd = FreeFile
    Open "WriteLocked.txt" For Binary As #fd
    ReDim buf(LOF(fd))
    Get #fd, , buf
    Close #fd

    With CreateObject("ADODB.Stream")
        .Type = 1   ' 1=adTypeBinary
        .Open
        .Write buf
        .Position = 0
        .Type = 2   ' 2=adTypeText
        .Charset = "UTF-8"
        ' ReadText...
        .Close
    End With
End Sub

この方法の欠点は、ファイル全体をメモリに読み込んでしまうこと。できれば 1 行ずつ読み込ませたいが、バッファリング制御の実装とか大変そう過ぎて、やってみる気すら起きない。

2016-02-03

ActiveDirectory DDNS + NetworkManager

現代の開発者にとって Linux 仮想マシンは必要不可欠なものだが、IP アドレスをどうするかはいつも悩ましい問題だ。例えば下記のような状況は多いはず。

  • 社内は ActiveDirectory DNS / DHCP 環境。
  • 勝手に固定 IP アドレスを使えない。
  • 社内ネットワークから仮想マシンを参照させたい。

このような場合は DHCP を使うことになると思うが、暫く起動しないと IP アドレスが変わって接続する際に一手間かかって面倒くさい。個人的にはこの様な場合、Samba (の nmbd)を使っている。サービスを起動しておくだけで Windows PC からホスト名(NetBIOS 名)でアクセスできるので、とても便利。

しかし最近、ActiveDirectory のドメインに参加せずとも、DHCP で DNS レコードを登録できることを知った。少なくとも ActiveDirectory の DNS / DHCP にはそういう機能があり、そして私の社内ではそれが有効になっている。そうすると後は DHCP クライアント側の話。RHEL では、次の設定でそれができる。(詳細は man dhclient.conf)

RHEL6: /etc/dhcp/dhclient.conf:

send fqdn.fqdn "myhost.example.local.";
send fqdn.server-update off;

RHEL5 ではファイルの場所が /etc/dhclient.conf となる。RHEL4 以前は知らない。

ネット上には、同じく dhclient.conf の host-name や ifcfg-* の DHCP_HOSTNAME でできるよ、みたいな情報も見つかる。しかし BIND ではそれで動くのかも知れない(未検証)が、社内の ActiveDirectory では動かなかった。

なお、dhclient.conf はデバイス(インターフェイス)毎に設定が可能で、その場合はグローバルな dhclient.conf は無視されるので注意。この辺はソースコードを見た方が理解が早い。

RHEL5.11: /etc/sysconfig/network-scripts/ifup-eth:

    # allow users to use generic '/etc/dhclient.conf' (as documented in manpage!)
    # if per-device file doesn't exist or is empty
    if [ -s /etc/dhclient-${DEVICE}.conf ]; then
       DHCLIENTCONF="-cf /etc/dhclient-${DEVICE}.conf";
    else
       DHCLIENTCONF='';
    fi;

RHEL6.7: /etc/sysconfig/network-scripts/network-functions:

generate_config_file_name () {
        local ver=$1
        if [ -s /etc/dhcp/dhclient$ver-${DEVICE}.conf ]; then
                DHCLIENTCONF="-cf /etc/dhcp/dhclient$ver-${DEVICE}.conf";
        elif [ -s /etc/dhclient$ver-${DEVICE}.conf ]; then
                DHCLIENTCONF="-cf /etc/dhclient$ver-${DEVICE}.conf";
        else
                DHCLIENTCONF='';
        fi
}

例えば私の環境では、RHEL5.11 のインストール直後に /etc/dhclient-eth0.conf が存在したので、この場合は /etc/dhclient.conf に設定を書いても無視される。

以上で、私の環境でも DNS にホスト名を登録できた。ただ個人的には、Windows PC からアクセスするだけなら Samba nmbd の方を勧めたい。わざわざ DNS レコードをゴニョゴニョせずに済むなら、それに越したことはない。

と、これで話が終われば幸せだった・・・。というか、こうして本エントリを書く必要すらなかった。残念ながら、以上は RHEL6 までの話。RHEL7 では話が変わる。:-(

周知の通り、RHEL7 では NetworkManager が幅を利かせていて、NetworkManager はシェルスクリプトで書かれた従来処理は使用せずに、自前で処理を行う。(ただし従来処理自体は存在していて、それらは NetworkManager が無効の時に使用される)

まず、dhclient.conf が未設定の状態でどう動くのか確認してみる。

[rhel7]# cat /etc/redhat-release
Red Hat Enterprise Linux Server release 7.2 (Maipo)

[rhel7]# rpm -q NetworkManager
NetworkManager-1.0.6-27.el7.x86_64

[rhel7]# cat /etc/dhcp/dhclient.conf
cat: /etc/dhcp/dhclient.conf: No such file or directory

このときデバイスの dhclient 設定は、デバイス起動時に下記の場所に生成される。

[rhel7]# cat /var/lib/NetworkManager/dhclient-enp0s17.conf
# NetworkManager で作成されています

send host-name "rhel7"; # added by NetworkManager

option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;
option ms-classless-static-routes code 249 = array of unsigned integer 8;
option wpad code 252 = string;

also request rfc3442-classless-static-routes;
also request ms-classless-static-routes;
also request static-routes;
also request wpad;
also request ntp-servers;

dhclient.conf を作ってデバイスを再起動すると、上記が再生成されるが・・・、

[rhel7]# cat /etc/dhcp/dhclient.conf
send fqdn.fqdn "rhel7.example.local.";
send fqdn.server-update off;

[rhel7]# systemctl restart network

[rhel7]# cat /var/lib/NetworkManager/dhclient-enp0s17.conf
# NetworkManager で作成されています
# /etc/dhcp/dhclient.conf からマージされています

send fqdn.server-update off;
send host-name "rhel7"; # added by NetworkManager

option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;
option ms-classless-static-routes code 249 = array of unsigned integer 8;
option wpad code 252 = string;

also request rfc3442-classless-static-routes;
also request ms-classless-static-routes;
also request static-routes;
also request wpad;
also request ntp-servers;

fqdn.fqdn がマージされていない!

ソースコードを確認してみる。

#define HOSTNAME4_TAG    "send host-name"
#define HOSTNAME4_FORMAT HOSTNAME4_TAG " \"%s\"; # added by NetworkManager"

#define FQDN_TAG    "send fqdn.fqdn"
#define FQDN_FORMAT FQDN_TAG " \"%s\"; # added by NetworkManager"

/* ...snip... */

char *
nm_dhcp_dhclient_create_config (const char *interface,
                                gboolean is_ip6,
                                GBytes *client_id,
                                const char *anycast_addr,
                                const char *hostname,
                                const char *orig_path,
                                const char *orig_contents,
                                GBytes **out_new_client_id)
{
	/* ...snip... */

			/* Override config file hostname and use one from the connection */
			if (hostname) {
				if (strncmp (p, HOSTNAME4_TAG, strlen (HOSTNAME4_TAG)) == 0)
					continue;
				if (strncmp (p, FQDN_TAG, strlen (FQDN_TAG)) == 0)
					continue;
			}

何故か fqdn.fqdn をスキップしている。かといってfqdn.fqdn が全て抹殺される訳でもなくて、

static void
add_hostname4 (GString *str, const char *format, const char *hostname)
{
	char *plain_hostname, *dot;

	if (hostname) {
		plain_hostname = g_strdup (hostname);
		dot = strchr (plain_hostname, '.');
		/* get rid of the domain */
		if (dot)
			*dot = '\0';

		g_string_append_printf (str, format, plain_hostname);
		g_free (plain_hostname);
	}
}

/* ...snip... */

static void
add_hostname6 (GString *str, const char *hostname)
{
	/* dhclient only supports the fqdn.fqdn for DHCPv6 and requires a fully-
	 * qualified name for this option, so we must require one here too.
	 */
	if (hostname && strchr (hostname, '.')) {
		g_string_append_printf (str, FQDN_FORMAT "\n", hostname);
		g_string_append (str,
		                 "send fqdn.encoded on;\n"
		                 "send fqdn.server-update on;\n");
		g_string_append_c (str, '\n');
	}
}

何故か IPv6 の方では fqdn.fqdn を設定している。IPv6 の動作は未確認だが、これだと IPv4 で fqdn.fqdn を送る術がないように見える。

もう NetworkManager ってほんと ks だわ、と思っていたら、

どうやら NetworkManager の設定項目に ipv4.dhcp-fqdn が増える模様。果たしてこれは RHEL7.3 に入ってくれるのだろうか。


2018-09-25 追記

あれから 2 年半、現段階での私の結論を記しておく。

まず、前述の修正は RHEL7.3 に入った。しかし私の環境では、全く状況は変わらなかった。これ以上はパケットキャプチャとか更なるソースコード解読とかが必要になるし、面倒いのでずっと放置していた。

その後、今度は dhclient のフックを使う方法を知った。

これは dhclient.conf に FQDN を記述するよりスマートな方法に思える。FQDN は hostname コマンドから引っ張ってこれるので、ハードコードが不要になるからだ。

しかし、これも動かなかった。orz

NetworkManager は dhclient のフックを呼ばないらしい。あれもダメ、これもダメ、一体どうしろというのか。NetworkManager 的には、「dispatcher を使え」とのこと。

/etc/NetworkManager/dispatcher.d/90-nsupdate:

#!/bin/bash

[ -n "$DHCP4_IP_ADDRESS" ] && [ "$HOSTNAME" != "${HOSTNAME/./}" ] \
    || exit 0

nsupdate <<EOF
update delete $HOSTNAME a
update add $HOSTNAME 3600 a $DHCP4_IP_ADDRESS
send
EOF

/etc/NetworkManager/dispatcher.d/pre-down.d/90-nsupdate:

#!/bin/bash

[ "$HOSTNAME" != "${HOSTNAME/./}" ] \
    || exit 0

nsupdate <<EOF
update delete $HOSTNAME a
send
EOF

後者の pre-down 処理は、対称性の観点(up 時に登録した名前を down 時に削除する)から作ったものの、実はあってもなくても同じ。何故なら、pre-down 処理はサーバーシャットダウン時に呼ばれない から。でもネットワーク再起動(systemctl restart network)では呼ばれる。これが仕様なのかバグなのかは分からないが、ほんとこれを作った奴はタヒんでいいよ。

あと、dispatcher 内で利用できる環境変数(例: DHCP4_IP_ADDRESS)は action (例: up, pre-down)毎に異なるのだが、それらは man を見てもいまいち明確でない。なので dispatcher を自作する場合は、まず set コマンドの出力をロギングする dispatcher を作り、利用可能な環境変数を確認してみることをお勧めする。

結論: やっぱり NetworkManager は ks。

2014-03-31

Python: Portablize Sphinx

そろそろ、以前から気になっていた Sphinx を触ってみようかと。スタンドアロン版ソフトウェアをこよなく愛す私は、当然のようにスタンドアロン版を選択。(以降、スタンドアロン版インストーラー SphinxInstaller-1.2.20131210-py2.7-win32.zip を使用)

しかし使ってみて、コレジャナイ・・・orz。うん、私が悪かった。「スタンドアロン」ではなくて、「ポータブル」と言うべきだった。上記はスタンドアロンではあるが、ポータブルではない。具体的には、インストールしたディレクトリを別の場所に移動すると動かなくなる。これでは zip で固めてメンバーへ配布、という訳にはいかない。

調べると、bin/sphinx-*-script.py 中に絶対パスが記述されているのが原因のようだ。特に shebang 行がまずい。shebang は bin/sphinx-*.exe が bin/sphinx-*-script.py を実行するために使用するのだと思うが、shebang は絶対パスでないと動かない。

ここで諦めようとも思ったが、何か悔しいので「起動時に毎回 sphinx-*-script.py を書き換える」という力技に出てみた。

$SPHINX_HOME/sphinx.bat:

@echo off
setlocal

set _dp0=%~dp0
set SPHINX_HOME=%_dp0:~0,-1%
set _dp0=
set PYTHON_HOME=%SPHINX_HOME%\python
set PATH=%PATH%;%SPHINX_HOME%\bin;%PYTHON_HOME%;%PYTHON_HOME%\Scripts

python.exe "%SPHINX_HOME%\bin\setup-portable.py" -x >NUL
if errorlevel 1 (
  echo ERROR: failed to setup
  pause
  exit 1
)
title PortableSphinx
echo *** Welcome to PortableSphinx! ***
echo.
cmd.exe /k

$SPHINX_HOME/bin/setup-portable.py:

#!python.exe

__doc__ = """\
Usage:
  python %s -x
Description:
  Setup PortableSphinx.  This should be executed first whenever the
  Sphinx directory is moved on your PC.
""" % __file__

import filecmp
import glob
import os
import re
import shutil
import sys
import tempfile

_sphinx_home = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

def _create_tempfile(lines=None):
    path = ''
    try:
        with tempfile.NamedTemporaryFile(delete=False) as fh:
            path = fh.name
            if lines:
                fh.writelines(lines)
        return path
    except StandardError:
        _remove_file(path)
        raise

def _remove_file(path):
    try:
        os.remove(path)
    except StandardError:
        pass

def _read_file(path):
    with open(path) as fh:
        return fh.readlines()

def _write_file(path, lines):
    tmp_path = _create_tempfile(lines)
    try:
        if not filecmp.cmp(tmp_path, path):
            shutil.copyfile(tmp_path, path)
    finally:
        _remove_file(tmp_path)

def _convert_script(lines):
    lines = iter(lines)
    for line in lines:
        yield '#!"%s"\n' % os.path.join(_sphinx_home, 'python', 'python.exe')
        break
    sphinx_home = _sphinx_home.replace('\\', '\\\\\\\\')
    re_library = re.compile(r"^(\s*').+?(?=\\\\(eggs|python)\\\\)", re.I)
    for line in lines:
        yield re_library.sub(r'\1' + sphinx_home, line)

def _setup_bin():
    script_glob = os.path.join(_sphinx_home, 'bin', 'sphinx-*-script.py')
    script_paths = glob.glob(script_glob)
    if not script_paths:
        raise RuntimeError('not found: %s' % script_glob)
    for script_path in script_paths:
        lines = _read_file(script_path)
        _write_file(script_path, _convert_script(lines))

def main():
    status = 1
    args = sys.argv[1:]
    if args and args[0] == '-x':
        _setup_bin()
        print 'OK'
        status = 0
    else:
        print __doc__,
    return status

if __name__ == '__main__':
    sys.exit(main())

スタンドアロン版 Sphinx のインストール先に上記を突っ込んで、毎回 sphinx.bat から起動するようにすれば、擬似 PortableSphinx の出来上がり。後はディレクトリを zip で固めればそのままプロジェクトメンバーに配布できる。今のところ、私が使う範囲で不具合は出ていない。(不具合がないとは言っていない)

さて、実際に Sphinx を使ってみると、確かにこれはなかなか良い。懸念していた日本語ファイル名(CP932)も問題ないし、印刷時の改頁も(組み込み機能にはないものの)制御可能。簡単なブロック図であれば Sphinx 上で生成できる。正に、至れり尽くせり。うん、きっとこれはハヤル。(実際にはずっと前から流行っている)

Sphinx の「ソースを編集 → make → 動作確認」という流れは、昨今のプログラマーには受け入れ難いだろうが、古き良き時代のプログラム開発みたいで老年プログラマーには逆に心地良い。私も最初はウキウキしながら make していたが、デザイン調整などで CSS を書き換える度に make するのは流石に面倒になってきた。人間って本当に我がままだと思う。

ということで、(誰もが考え付くであろう)変更を自動で検知して make してみる。

$SPHINX_HOME/bin/makebot.bat:

@echo off
setlocal

:loop
echo [%~n0] watching... press Ctrl-C to abort
python.exe "%~dp0notify-changed.py" .
if errorlevel 1 exit /b 1
echo [%~n0] detected
call make clean && call make html
echo.
goto loop

$SPHINX_HOME/bin/notify-changed.py:

#!python.exe

__doc__ = """\
Usage:
  python %s DIRECTORY
Description:
  Watch and wait for changes in a DIRECTORY and ends with status 0.
""" % __file__

import fnmatch
import os
import sys
import time

_include_patterns = ('*.conf', '*.css', '*.css_t', '*.html', '*.py', '*.rst')
_exclude_patterns = ('_*',)

def _fnmatch_any(name, patterns):
    for pattern in patterns:
        if fnmatch.fnmatch(name, pattern):
            return True
    return False

def _filter_name(names, includes=None, excludes=None):
    if includes:
        names = [x for x in names if _fnmatch_any(x, includes)]
    if excludes:
        names = [x for x in names if not _fnmatch_any(x, excludes)]
    return names

def _filter_dir(names):
    return _filter_name(names, excludes=_exclude_patterns)

def _filter_file(names):
    return _filter_name(names,
                        includes=_include_patterns,
                        excludes=_exclude_patterns)

def _find_files(path):
    for dirpath, dirnames, filenames in os.walk(path):
        dirnames[:] = _filter_dir(dirnames)
        for filename in _filter_file(filenames):
            yield os.path.join(dirpath, filename)

def _iter_filestats(path):
    for filepath in _find_files(path):
        yield filepath, int(os.stat(filepath).st_mtime)

def _diff_filestats(before, after):
    for filepath, mtime in after.iteritems():
        if mtime != before.get(filepath, 0):
            return True
    return False

def _watch_directory(path, interval=1):
    if not os.path.isdir(path):
        return "ERROR: directory not found: `%s'" % path
    before = dict(_iter_filestats(path))
    while True:
        time.sleep(interval)
        after = dict(_iter_filestats(path))
        if _diff_filestats(before, after):
            break
        before = after
    return 0

def main():
    status = 1
    args = sys.argv[1:]
    if args:
        try:
            status = _watch_directory(args[0])
        except KeyboardInterrupt:
            pass
    else:
        print __doc__,
    return status

if __name__ == '__main__':
    sys.exit(main())

pywin32 辺りを使えばファイル変更を検知する API が使えたりするのかも知れないが、生憎 Sphinx には pywin32 が添付されていないので、力技で実装。数千くらいのファイルなら問題ないはず(数万とかは知らない)。後は Ctrl-C で終了すると「バッチ ジョブを終了しますか (Y/N)?」とか聞かれるのがウザいが、これは Windows BAT のクソ仕様の問題だし、基本 makebot しっ放しなので我慢する。

取りあえず、これで私の Sphinx on Windows 環境は幸せになった。

2014-02-24

Java: Import a certificate with `keytool'

ここ最近、Java のセキュリティ名目での やんちゃ ぶりには、振り回されている人も多いのではないだろうか。私も、Java 7 Update 40 以降でアプレット署名に自己証明書が使えなくなり、その対応に煩わされた。

クライアント PC に証明書をインポートする作業は、keytool で自動化できる。自己証明書の時はそれで問題なかったのだが、正規の証明書に変えた途端、上手くいかなくなった。具体的には、証明書をインポートしてもアプレット実行時に出てくるダイアログを抑止できない。

証明書の作り方に問題があるのかも知れないが、生憎とそこは別の人の仕事。まずは渡された証明書で何とかする方法を探してみる。(恐らくは証明書の Common Name が怪しいと睨んでいるが、未検証)

私を混乱させたのは下記の挙動。

  1. Java コントロールパネルから証明書をインポートしても、アプレット実行時にダイアログが出てきてしまう。
  2. ダイアログで「次回から表示しない」を選択して実行すると、ダイアログは二度と出てこない。
  3. (1)と(2)についてそれぞれ証明書をエクスポートすると、両者は完全に一致する。

つまりエクスポートした証明書は全く同じなのに、ダイアログが出る or 出ない、という違いが出る。インポート方法を色々と試してみたが、結局(2)以外でダイアログを抑止することはできなかった。最後に、ダメ元で気になっていたことを試してみた。実は(1)と(2)では、1 つだけ違いがある。それがキーストア中の「別名」(alias)表示だ。これも keytool で見ることができる。

Java コントロールパネルから証明書をインポートした場合:

別名: deploymentusercertnullnullnulljava.util.random@1a36121

ダイアログで「次回から表示しない」を選択した場合:

別名: deploymentusercert$tsflag$loc=http//example.com:80##docbase:http//example.com:80java.util.random@10acb9b

後者に、怪しげな呪文が見て取れる。ダメ元で後者の「別名」でインポートしてみると・・・、

やったよ、ビンゴ! :-D

もうね、何でこんな仕様なのかと。確かに前述のダイアログには「上記の発行者と場所」とあったが、まさか「場所」をこんな方法で覚えるとか難度が高過ぎだろう。

色々と試した結果、「別名」は次の形式が必要なようだ。(Java 7 Update 51 にて確認)

  • $tsflag$loc=http//example.com:80##docbase:http//example.com:80
  • 「http//example.com:80」は適宜変更。ポートは省略不可。SSL なら「https//example.com:443」とする。
  • パースに問題なさそうな文字列であれば、先頭・末尾に追加可能。途中に入れることも可能だが、やらない方が無難だと思う。

以上を踏まえて、証明書インポート BAT の例。

importcert.bat:

@echo off
setlocal

set SITE_URL=http//example.com:80
set CERT_FILE=%~dp0mycert.cer
set KS_EMPTY_FILE=%~dp0trusted.certs.empty
set KS_ALIAS=deploymentusercert$tsflag$loc=%SITE_URL%##docbase:%SITE_URL%

set JAVA_VERSION=7
set JAVA_HOME=%ProgramFiles(x86)%\Java\jre%JAVA_VERSION%
if not exist "%JAVA_HOME%" (
  set JAVA_HOME=%ProgramFiles%\Java\jre%JAVA_VERSION%
)
set keytool=%JAVA_HOME%\bin\keytool.exe

set LocalLow=%USERPROFILE%\AppData\LocalLow
if not exist "%LocalLow%" (
  set LocalLow=%AppData%
)
set ks_dir=%LocalLow%\Sun\Java\Deployment\security
set ks_file=%ks_dir%\trusted.certs

set result=1

if not exist "%keytool%" (
  echo ERROR: not found: "%keytool%"
  goto exit
)
if not exist "%CERT_FILE%" (
  echo ERROR: not found: "%CERT_FILE%"
  goto exit
)
if not exist "%ks_file%" (
  mkdir "%ks_dir%" 2>NUL
  copy /v "%KS_EMPTY_FILE%" "%ks_file%"
  if not exist "%ks_file%" (
    echo ERROR: not found: "%ks_file%"
    goto exit
  )
)

"%keytool%" -list ^
            -alias "%KS_ALIAS%" ^
            -keystore "%ks_file%" ^
            -storepass "" >NUL
if not errorlevel 1 (
  echo already imported.
  goto success
)

"%keytool%" -importcert -v ^
            -alias "%KS_ALIAS%" ^
            -file "%CERT_FILE%" ^
            -keystore "%ks_file%" ^
            -storepass "" ^
            -noprompt
set result=%ERRORLEVEL%
if %result% neq 0 (
  echo ERROR: keytool.exe: code=%result%
  goto exit
)

:success
set result=0

:exit
if %result% equ 0 (
  echo OK
) else (
  echo Failed
)
pause
exit /b %result%

実行には上記 BAT に加え、下記ファイルが必要。

  • mycert.cer (インポートする証明書)
  • trusted.certs.empty (空のキーストアファイル)

詳細は BAT を解読して貰うとして、ファイル「trusted.certs.empty」については説明が必要だと思う。

keytool は、キーストアファイルが存在しなければ新たにファイルを作成する。しかしこの時、何故かパスワードを強要してくる(-storepass "" は効かない)ため、パスワードなしキーストアを作ることができない。一方 Java コントロールパネルから証明書をインポートすると、パスワードなしキーストアが作成される。つまり、Java コントロールパネルはパスワードなしキーストアを使うくせに、keytool からはそれを作ることができないのだ。もうほんと、この仕様を作った奴はタヒねと言いたい。

trusted.certs.empty は、予め作成した空のパスワードなしキーストアだ。Java コントロールパネルで適当な証明書をインポート → 削除すれば作成できる(ファイル場所は %ks_file% を参照)。私が確認した限り、Windows XP と Windows 8 とでファイルは完全に一致したので、今後の互換性も問題ないだろう。

下記は、インポート済み証明書を一覧表示する BAT の例。「別名」もこれで確認できる。

listcerts.bat:

@echo off
setlocal

set JAVA_VERSION=7
set JAVA_HOME=%ProgramFiles(x86)%\Java\jre%JAVA_VERSION%
if not exist "%JAVA_HOME%" (
  set JAVA_HOME=%ProgramFiles%\Java\jre%JAVA_VERSION%
)
set keytool=%JAVA_HOME%\bin\keytool.exe

set LocalLow=%USERPROFILE%\AppData\LocalLow
if not exist "%LocalLow%" (
  set LocalLow=%AppData%
)
set ks_dir=%LocalLow%\Sun\Java\Deployment\security
set ks_file=%ks_dir%\trusted.certs

set result=1

if not exist "%keytool%" (
  echo ERROR: not found: "%keytool%"
  goto exit
)
if not exist "%ks_file%" (
  echo ERROR: not found: "%ks_file%"
  goto exit
)

"%keytool%" -list -v ^
            -keystore "%ks_file%" ^
            -storepass ""
set result=%ERRORLEVEL%
if %result% neq 0 (
  echo ERROR: keytool.exe: code=%result%
)

:exit
pause
exit /b %result%

今回はこれで解決だが、問題は、今後の互換性は保証されない ということ。

事実、Java 7 Update 45 → 51 で「別名」の形式が変わった。具体的には、u51 で「##docbase:...」が増えた。このせいで、u45 で証明書をインポートしていても、u51 にアップデートすると再びダイアログが出てきてしまう。ここまで来ると、分かってて嫌がらせをしているとしか思えない。(u51 アップデート時に出てくるダイアログで「セキュリティ・プロンプトの復元」に初期でチェックが入っているのも、これを隠すための陰謀だと思っている)

そろそろ、世界中の IT エンジニアは Java の横暴に NO! って言っても良い頃だと思う。

参考:


2014-04-28 追記

Java 7 Update 55 にて、更にマジックワード「##from」が増えた模様。

  • $tsflag$loc=http//example.com:80##docbase:http//example.com:80##from:http//example.com:80

2013-09-30

xyzzy: zgrep.l

エディターに Emacs を常用するようになって久しいが、その前は xyzzy を使っていた。更にその前は WZ Editor で、社会人プログラマーになって最初のエディターが WZ だった。WZ から xyzzy へ移行した動機は省くが、移行で最後までネックになったのが Grep。

使った人にしか分からないが、WZ Grep には該当行の前後を確認できるプレビュー画面があって、大量の Grep 結果を見る際に非常に便利だった。偶に秀丸などを使う機会があると、プレビューなしでどうやって仕事ができるのか不思議に思ったくらいだ。

結局、xyzzy を使いながら WZ Grep からも離れらない状況が続き、いい加減 WZ と決別するために拡張 Lisp を自作したのだった。当時、既に WZ Grep を模倣する拡張 Lisp は存在したが、使ってみて「これじゃない」感が強くて常用には至らなかった。

以上は 10 年も前のことなので今更ではあるが、その拡張 Lisp をここに公開しておく。ファイルの最終更新日は 2004-09-07。もうコードの内容も覚えてないし、読む気も更新するつもりもないが、WZ Grep から離れられない人の気持ちは良く分かるので。

今でも、Windows で大量のファイルを Grep する必要に迫られた時には、これを使うことがある。Emacs でも作れば良いのだが、今の仕事だと無くてもそれ程には困らないので、作るまでのモチベーションがない・・・。

$XYZZY/site-lisp/zgrep.l:

;;; -*- Mode: Lisp -*-
;;;
;;; This file is NOT part of xyzzy.
;;;
;;
;; 概要
;; ======
;;
;; VZ Grep / WZ Grep のように、Grep バッファにプレビューウィンドウを付ける
;; メジャーモード。
;;
;; 設定
;; ======
;;
;; .xyzzy 又は siteinit.l に下記を記述。
;;
;;   (require "zgrep")
;;   (zgrep-default-setting)
;;
;; `zgrep-default-setting' には、次の設定が含まれている。
;;
;;   ;; SPC でプレビューウィンドウをトグル
;;   (define-key *zgrep-mode-map* #\SPC 'zgrep-toggle-preview)
;;
;;   ;; RET で該当ファイルを開く
;;   (define-key *zgrep-mode-map* #\RET 'zgrep-open)
;;
;;   ;; カーソル移動で次々にプレビューする
;;   (define-key *zgrep-mode-map* #\Up 'zgrep-previous-line)
;;   (define-key *zgrep-mode-map* #\Down 'zgrep-next-line)
;;
;; その他の設定例。
;;
;;   ;; Grep 後、自動で zgrep-mode にする
;;   (defun my-grep-hook ()
;;     (delete-other-windows)
;;     (zgrep-mode (car *minibuffer-search-string-history*)))
;;   (add-hook 'ed::*grepd-hook* 'my-grep-hook)
;;   (add-hook 'ed::*grep-hook* 'my-grep-hook)
;;
;;   ;; Grep ウィンドウ(上段)のサイズ
;;   (setq *zgrep-window-size* 10)
;;
;;   ;; リードオンリーでファイルを開く
;;   (setq *zgrep-open-read-only* t)
;;
;;   ;; Grep 文字色
;;   (setq *zgrep-foreground-color* 1)
;;   (setq *zgrep-background-color* 14)
;;   (setq *zgrep-bold* t)
;;
;;   ;; 拡張子 .grep のファイルを zgrep-mode で開く
;;   (push '("\\.grep$" . zgrep-mode) *auto-mode-alist*)
;;
;; その他
;; ======
;;
;; * ファイル全体を読み込むため、巨大なファイルのプレビューには難有り。
;; * プレビューウィンドウ中での該当行の反転は、色設定によっては見難い。
;; * Grep を C-g で中断したとき zgrep-mode にする上手い方法。
;;

(provide "zgrep")

(defvar *zgrep-mode-hook* nil)

(defvar *zgrep-mode-map* nil)
(unless *zgrep-mode-map*
  (setq *zgrep-mode-map* (make-sparse-keymap)))

(defvar *zgrep-window-size* *error-window-size*)
(defvar *zgrep-open-read-only* nil)

(defvar-local *zgrep-grep-string* nil)
(defvar *zgrep-foreground-color* 1)
(defvar *zgrep-background-color* nil)
(defvar *zgrep-bold* nil)

(defvar-local *zgrep-mode* nil)
(defconstant zgrep-mode-name "zgrep:~A")
(defconstant zgrep-preview-buffer "*grep preview*")
(defvar *zgrep-preview-filename* nil)

(defun zgrep-set-string-color (str &optional from to)
  (if (and str
	   (/= (length str) 0)
	   (or *zgrep-foreground-color*
	       *zgrep-background-color*
	       *zgrep-bold*))
      (save-excursion
	(save-restriction
	  (narrow-to-region (or from (point-min)) (or to (point-max)))
	  (goto-char (point-min))
	  (while (scan-buffer (concat "\\(" str "\\)")
			      :regexp t :no-dup t :case-fold t)
	    (set-text-attribute (match-beginning 1) (match-end 1)
				'zgrep-string
				:foreground *zgrep-foreground-color*
				:background *zgrep-background-color*
				:bold *zgrep-bold*))))))

(defun zgrep-delete-preview-buffer ()
  (let ((buf (find-buffer zgrep-preview-buffer)))
    (if buf (delete-buffer buf)))
  (setq *zgrep-preview-filename* nil))

(defun zgrep-make-preview-buffer (filename)
  (let ((old-buf (selected-buffer))
	(preview-buf (find-buffer zgrep-preview-buffer))
	(grep *zgrep-grep-string*))
    (unless (and preview-buf
		 (string= filename *zgrep-preview-filename*))
      (zgrep-delete-preview-buffer)
      (setq preview-buf (get-buffer-create zgrep-preview-buffer))
      (set-buffer preview-buf)
      (insert-file-contents filename)
      (dolist (x *auto-mode-alist*)
	(when (string-matchp (car x) filename)
	  (funcall (cdr x))))
      (zgrep-set-string-color grep)
      (set-buffer-modified-p nil)
      (setq buffer-read-only t))
    (set-buffer old-buf)
    (setq *zgrep-preview-filename* filename)
    preview-buf))

;; returns (file line body)
(defun zgrep-parse-line ()
  (save-excursion
    (let ((str (buffer-substring (progn (goto-eol) (point))
				 (progn (goto-bol) (point)))))
      (if (string-match "^\\([^:]+\\):\\([0-9]+\\):\\(.*\\)$" str)
	  (list (substring str (match-beginning 1) (match-end 1))
		(parse-integer (substring str
					  (match-beginning 2)
					  (match-end 2)))
		(substring str (match-beginning 3) (match-end 3)))))))

;; Emacs like `get-buffer-window'
(defun zgrep-get-buffer-window (buffer-or-name)
  (ignore-errors
    (get-buffer-window buffer-or-name (selected-window))))

(defun zgrep-preview-p ()
  (zgrep-get-buffer-window zgrep-preview-buffer))

(defun zgrep-preview ()
  (interactive)
  (let ((parsed (zgrep-parse-line)))
    (when (and parsed
	       (file-exist-p (nth 0 parsed)))
      (let ((preview-buf (zgrep-make-preview-buffer (nth 0 parsed))))
	(delete-other-windows)
	(split-window *zgrep-window-size*)
	(other-window)
	(set-buffer preview-buf)
	(goto-line (nth 1 parsed))
	(reverse-region (progn (goto-eol) (point))
			(progn (goto-bol) (point)))
	(refresh-screen)
	(recenter)
	(other-window)
	(recenter)))))

(defun zgrep-toggle-preview ()
  (interactive)
  (if (zgrep-preview-p)
      (progn
	(delete-other-windows)
	;;(zgrep-delete-preview-buffer)
	)
    (zgrep-preview)))

(defun zgrep-find-file (filename)
  (let ((old-buffer (get-file-buffer filename)))
    (if old-buffer
	(progn
	  (ed::find-file-verify old-buffer filename nil nil nil nil)
	  (set-buffer old-buffer))
      (if *zgrep-open-read-only*
	  (find-file-read-only filename)
	(find-file filename)))))

(defun zgrep-open ()
  (interactive)
  (let ((parsed (zgrep-parse-line)))
    (when (and parsed
	       (file-exist-p (nth 0 parsed)))
      ;;(zgrep-delete-preview-buffer)
      (zgrep-find-file (nth 0 parsed))
      (delete-other-windows)
      (goto-line (nth 1 parsed))
      (refresh-screen)
      (recenter))))

(defun zgrep-set-grep-string (grep)
  (interactive "sGrep string (Regexp): ")
  (setq *zgrep-grep-string* grep)
  (zgrep-set-string-color grep))

(defun zgrep-next-line-internal (n)
  (ed::next-line-1 n #'forward-virtual-line #'goto-virtual-column))

(defun zgrep-next-line (&optional (n 1))
  (interactive)
  (let ((old-line (current-line-number)))
    (zgrep-next-line-internal n)
    (when (and (/= old-line (current-line-number))
	       (zgrep-preview-p))
      (zgrep-preview))))

(defun zgrep-previous-line (&optional (n 1))
  (interactive)
  (zgrep-next-line (- n)))

(defun zgrep-mode-toggle ()
  (interactive)
  (if *zgrep-mode*
      (progn
	(setq mode-name (format nil zgrep-mode-name "off"))
	(use-keymap (make-sparse-keymap))
	(local-set-key '(#\C-c #\C-c) 'zgrep-mode-toggle))
    (progn
      (setq mode-name (format nil zgrep-mode-name "on"))
      (use-keymap *zgrep-mode-map*)))
  (setq *zgrep-mode* (null *zgrep-mode*)))

(defun zgrep-mode (&optional grep)
  (interactive)
  (kill-all-local-variables)
  (setq buffer-mode 'zgrep-mode)
  (zgrep-mode-toggle)
  (setq *zgrep-grep-string* grep)
  (run-hooks '*zgrep-mode-hook*))

(defun zgrep-default-setting ()
  (define-key *zgrep-mode-map* #\SPC 'zgrep-toggle-preview)
  (define-key *zgrep-mode-map* #\RET 'zgrep-open)
  (define-key *zgrep-mode-map* #\Up 'zgrep-previous-line)
  (define-key *zgrep-mode-map* #\Down 'zgrep-next-line)
  (define-key *zgrep-mode-map* #\S 'zgrep-set-grep-string)
  (define-key *zgrep-mode-map* #\R 'rename-buffer)
  (define-key *zgrep-mode-map* '(#\C-c #\C-c) 'zgrep-mode-toggle))

2013-05-07

Can't install IE9 offline on Windows 7

下記の環境で、

IE9 のインストールに失敗する。スタンドアロン版インストーラーのくせに、こともあろうかインストール時に Windows Update に繋ごうとして失敗している。オプション /update-no や /passive も試してみたが効果なし。こういう時に /update-no が使えなくて、いつ使えというのだろう。

IE9 がインストールできない現象は様々にあるようだが、今回の場合、

で記載されている下記の更新プログラムをインストールすることで解決した。

こういう面倒を見てくれる所まで含めて、スタンドアロン版インストーラーと言うのではなくて?

2011-12-15

VMware Server 1.x + CentOS 5.x

久しぶりに VMware で CentOS を立てたら、幾つか躓いてしまった。今まで散々同じことやったはずなのに、ちょっとやらないとコロッと忘れてしまう。

  • VMware Server 1.0.10
  • Host OS: Windows XP
  • Guest OS: CentOS 5.7 (i386)

まず、稼動後の翌日になっても来るはずのメールが来ない。cron でメール出してるはずなのに。おかしいなーって調べたら、ゲスト OS の時刻がようやく 0 時を回ったところだった。定番の、VM の時刻が遅れるってやつ。うわー恥ずかしー(赤面)。

CentOS 5.7 は「No additional kernel parameters required」って書いてあるけど、これは嘘だね。少なくとも今回の環境ではバッチリ遅れる。なので「divider=10」は問答無用で付けるのが吉。

次に、ホストの共有フォルダ <-> ゲスト間でファイルをネットワークコピーすると速攻でエラーに。あー、あったね、そんなの。

一番上の解法は今まで試したことなくて、VMware 設定で完結して楽そうだったからやってみたけど、嘘だったね。確かにコピーはできたけど、できないときもある。なのでホスト側の DoS 検知を無効にするの一択で。「Gigabit スイッチを使え」とかいうのも見えるけど、これも効果があるか極めて怪しいね。