daemon
前回調べたatが色々な名前に変身出来る件。どんな仕組みか調べてみる。atのソースを/tmpに 持ってきてコンパイル。色々足りないって言われた。言われるままに、cronのソースから取り寄せていたら、コンパイル出来た。
これは、きっとatのソース外に、__progname が定義されてるに違いない。持ってきたものの 中から探したら、
ob6$ grep __prog *.h globals.h:extern char *__progname;
以前やった、envの類と一緒だな。取り合えず、テストプログラムに放り込んでみる。
ob6$ cat test.c
#include <stdio.h>
extern char *__progname;
int main(){
printf("%s\n", __progname);
return 0;
}
ob6$ cc test.c
そして、実行。
ob6$ ./a.out a.out ob6$ mv a.out hogefuga ob6$ ./hogefuga hogefuga
アプリ名を変えても、フォローしてくれてる。
/usr/src/lib/libc/dlfcn/init.c:
/* * In dynamicly linked binaries environ and __progname are overriden by * the definitions in ld.so. */ char **environ __attribute__((weak)) = NULL; char *__progname __attribute__((weak)) = NULL;
疑問が氷解しました。これって、OpenBSD固有の事?
ob6$ grep __progname at.c | head -2
shortformat = strcmp(__progname, "at") == 0;
openlog(__progname, LOG_PID, LOG_CRON);
ob6$ cc -E at.c | grep __progname | head -2
extern char *__progname;
shortformat = strcmp(__progname, "at") == 0;
ローカルなヘッダーファイルは、要注意だな。個別に調べなくても、cppで展開したやつを荒く調べておけば、吉。
Windows golang
前回やった、お手製のタイマー。Windowsで実現するとしたらbatファイルを作るのだな。 だって、それだけの為に、タイマーを入れて、Windowsを汚したくない。じゃ自作だな。 それで、必要なのは、sleepとwallか。残念ながら両方共Windowsには無い。移植出来ないじゃん。まてまて、探してみはなれ。
Windowsの「timeout」「sleep」コマンドでバッチファイルの実行を一時停止するこれ、使えそう。
後はwall相当。wallの正式名称は、write a message to all users だった。でもオイラーのつたない英語辞書では、壁ってのが登録されてるよ。一応辞書したら、障壁なんて意味もあるのね。正に移植に立ちはだかる障壁だな。
で、ふと思ったんだ。画面一杯に壁を拡げて、そこに休憩とかと書いておけばいいじゃん。 ノートパッドを立ち上げて、windowを精一杯広げる。そこに、墨痕鮮やかに、休憩と書く。 後はそれを保存。時間待ちした後、ノートパッドで、そのファイルを読み出せば、いくらなんでも、気が付くだろう。ノートパッドはそのために有るんです。それ以外の使い道は残念ながら、ありません。
でも、こんなおバカなバッチは書かないぞ。
テキストからモールス信号のwavファイルを生成する『cwwav』コマンドなんてので音源を作り、それを鳴らすなんて言う、偏向した方法も一応可能。
普通の人なら、 golangを知った今、Windowsにもgoを入れて、豪華に、上の妄想と同じ事をやってみよう。何たって、Windowsネイティブなアプリが簡単に作成出来るはずですから。芸の肥やしになるだろう。
入れたぞ。ソースを書いたり、実行したりが面倒。だったら、WSLと組み合わせちゃえ。 そうすれば、emacs上でソースを書ける。実行は、Windowsにとんぼ返りして行う?
馬鹿言ってんじゃないよ。一度emacsを立ち上げたら、そこから出ないで何とかするのが、emacs屋さんの流儀。プロフェッショナルなるの流儀ってもんです。Makefileを書いておけば、 コンパイルコマンド一発で、実行出来るよ。
(define-key global-map (kbd "C-c c") 'compile)
こういう設定を .emacs.d/init.elに書いておく。そうすれば、Ctl-C c で、簡単にコンパイルする為にmakeを呼び出せる。
go屋さん流のMakefileなんてのが有った。そうでしょ、そうでしょ。オイラーもちょいと書いておくかな。
SRC= bt.go
run:
go.exe run ${SRC}
build:
go.exe build -o a.exe ${SRC}
./a.exe
clean:
rm -f a.exe
指定するアプリは、go.exeってのがポイントかな。WSL上でも、ちゃんとWindows側のgoが起動してくる。
sakae@atom:/mnt/c/work$ cat bt.go
// break time
package main
import (
"os/exec"
"time"
)
func main() {
time.Sleep(60 * time.Second)
cmd := exec.Command("notepad", "info.txt")
cmd.Run()
}
info.txtは、休憩と大書したファイル。これで不格好ながらも、壁の代わりが出来る。
C-C c すると make -k まで聞いてくるから、MakefileのTAGを指定する。(一番最初に定義したTAGの場合は省略可能)
Compile command: make -k build
後は、画面が割れて、コンパイルの進行状況と、実行結果が表示される。エラーが発生しても 簡単に修正と再実行が可能。なお、-kのオプションは、致命的なエラーじゃ無い場合、頑張ってコンパイルを続けるよってオプション。一度のコンパイルで、多数のエラーを検出出来るぞ。
-*- mode: compilation; default-directory: "/mnt/c/work/" -*- Compilation started at Sat Sep 15 14:08:23 make -k build go.exe build -o a.exe bt.go ./a.exe Compilation finished at Sat Sep 15 14:08:41
なお、emacsからgo-modeを使っていると、M-x godoc で、任意のパッケージの説明を参照出来る。でも、折角Windows用のgoが入っているんで、cmd端末から
c:\work>godoc -http=:8080
を実行して、http版のgodocサーバーを起動。 http://localhost:8080/ で、アクセスすれば良い。各パッケージに例が載ってて、これを コピペして使うと便利だぞ。
上のMakefileで作成される、a.exe は、残念ながらCUIのアプリになっている。ダブルクリックして起動しても、不格好な黒い窓が出て来るぞ。ちょっとかっこ悪いな。 GUI版にするには、下記が参考になるかな。
そして、転ばぬ先の杖、 Goのpanicと向き合う
sound on Windows10
Windows10で音を鳴らすには、Grooveミュージックなんてアプリを使う。ソースを見てたりする時、CDを聞いてたりする。きっとwaveファイルも聞けるんだろうね。
CUIの端末からでも大丈夫かな。兎も角アプリの在り方が分からんと始まらない。検索したけど見つからず。だったら削除時にファイルの絶対PATHを指定するはず。削除の検索ワードを付けたらどう? そんな事で、在処の調べ方は下記のようにするみたい。
c:\work>powershell
Windows PowerShell
Copyright (C) Microsoft Corporation. All rights reserved.
PS C:\work> get-appxpackage *Microsoft.ZuneMusic*
Name : Microsoft.ZuneMusic
Publisher : CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmon
d, S=Washington, C=US
Architecture : X64
ResourceId :
Version : 10.18081.11121.0
PackageFullName : Microsoft.ZuneMusic_10.18081.11121.0_x64__8wekyb3d8bbwe
InstallLocation : C:\Program Files\WindowsApps\Microsoft.ZuneMusic_10.18081.1
1121.0_x64__8wekyb3d8bbwe
IsFramework : False
PackageFamilyName : Microsoft.ZuneMusic_8wekyb3d8bbwe
PublisherId : 8wekyb3d8bbwe
IsResourcePackage : False
IsBundle : False
IsDevelopmentMode : False
Dependencies : {Microsoft.VCLibs.140.00_14.0.26706.0_x64__8wekyb3d8bbwe, M
icrosoft.ZuneMusic_10.18081.11121.0_neutral_split.language-
ja_8wekyb3d8bbwe, Microsoft.ZuneMusic_10.18081.11121.0_neut
ral_split.scale-125_8wekyb3d8bbwe}
IsPartiallyStaged : False
SignatureKind : Store
Status : Ok
何これ? リナなんかで言うパッケージ情報じゃん。
仰せに従ってインストール場所に行こうとしたら、アクセス制限に阻まれた。突破しようと思えば何とかなるだろうけど、ヘタにいじって壊したくない。諦めムード。
何気に音源ファイル(debian様の所で作ったモールス音ファイル)を、ダブルクリックしたら、 Grooveミュージックが立ち上がって、音が聞こえてきた。これって、音ファイルのサフィックスで、起動するアプリが決定されるって事だよね。 Windowsの事、きっとMacの真似を徹底的にしてるに違いない。
c:\work>break.wav
こんな風に音源だけ指定したら、勝手にアプリが立ち上がって、音が聞こえた。やっぱりファイルを何のアプリで開いたらいいか知ってんだな。それにしてもshellで、いきなりデータファイルを指定して、アプリが起動するってUnix側の住人(CUIの人間)には、信じられない挙動です。
これに気を良くして、例のタイマーに組み込んでみた。"/C" を挟んでやるのが味噌かな。
cmd := exec.Command("cmd.exe", "/C", "break.wav")
cmd.Run()
でも、Grooveミュージックの制御画面が盛大に出て来て美しくない。CUIでさり気なく音するアプリってあるのかな? 探してみたけどWindowsはGUI命って事で以外に無いんだよな。GUIは使って天国作って地獄ってのを、初期のMACで経験してるオイラーには信じられないです。
まあオイラーの方針を押し付ける訳にもいかないので、使うとすれば、このあたりかなあ。 mpg123 - Fast console MPEG Audio Player and decoder library
ネットサーフィンしてる時、CDをかけている。ABBAとかユーロビート等の軽快な音楽。エコノミック症候群にならないように、軽くステップを踏んでいるのさ。お前の場合は、ステップじゃなくて、単なる貧乏ゆすりだろ、ってのはさておき、疑問発生。
先に動いているGrooveに先住権が有って、後からモールス音を鳴らそうとしても拒否されるんじゃなかろうか? いわゆる排他制御問題。 そんなの、どうなるかやってみた方が早い。
先に立ち上がってCD演奏してるのに、無粋なモールスが割り込んできたぞ。そして、CDの演奏は停止されたよ。そんなもんなんですかね。
最後にこれを実用に供しようとして、若干手直しした。
sakae@atom:~$ cat /mnt/c/work/bt.go
// break time
package main
import (
"os/exec"
"time"
"fmt"
)
func main() {
dt := 45 * time.Minute
fmt.Printf("\nWait %v and morse sound\n", dt)
time.Sleep(dt)
cmd := exec.Command("cmd.exe", "/C", "C:\\app\\break.wav")
cmd.Run()
このアプリをDeskTopに置くんで、音源の場所は固定にした。ダブルクリックで黒い窓が出て来るんだけど、余りに不愛想なので、ちょいとメッセージを表示させた。起動したら、窓はタスクバーに仕舞っちゃうんだけど、カウントダウンの時間を、そこはかとなく表示した方が良かったかな? そんなに凝ってもしょうがないって。
あっ、今気が付いた。CD1枚の演奏時間って、大体45~50分ぐらいじゃん。
switch to CentOS
前回lxde風のウブにgolangを入れた。上でやったようにemacsと共に使おうってんで、emacsも 導入したんだ。そして、.emacs.dとかをサーバー版のウブからscpしてきた。
でも、なぜか、.emacs.d/init.el が機能してなかった。んでもって、go-modeにも成れず。 なんじゃこりゃって思って調べたら、emacs導入時に勝手に、.emacsが入っちゃったみたい。 どうも信用ならんな。
えーいい、lxde使うの止めよう。違った系統のOSに入れ替えよう。そんでもって選んだのが、赤帽風職業的OSである、CentOSです。
VMWarePlayerに入れたんだけど、Fedora一族って見做されて特別コースが走った。でも、ライブ版になってるんで、HDDに入れるには、システムメニューから、選択しなければならなかった。そんなものなんかね。
で、無事に起動してGnomeを拝んでから、独自のパッケージをyumした。取り合えずは .emacs.dと.tmux.confをよそからscpしたよ。
パンチを喰らったぞ。go-modeを起動しようとしたら、何とか言うシンボルが見つけられないと言われた。ググルと色々な対策が出てた。でも、オイラーの所とはちっともマッチしない。
ふと、emacsのverを確認すると、24系と古かった。コピー元は26系。バージョン間で互換性の 無い変更が有ったんだろうなと推測。一度消して入れ直した。そしたら、24系にフィットするelcが出来たみたいで、正常に動き出した。
もう一つのパンチは、.tmux.confの非互換性で、設定が反映されない問題。設定例が無いかと システム内を彷徨って、手がかりを見つけた。変更してやっと動き出した。
#bind-key -T prefix Space next-window bind-key Space next
バージョンダウン時の忌々しい事例でした。そんなに新しい事をしたいかね? 少し保守的なものも並行に動かしておけって事かな。
become daemon
前回やったcronで、ダエモン君に簡単に変身してた。変身の術が公開されてるので、参考に見ておく。
/usr/src/lib/libc/gen/daemon.c
daemon(int nochdir, int noclose)
{
int fd;
switch (fork()) {
case -1:
return (-1);
case 0:
break;
default:
_exit(0);
}
if (setsid() == -1)
return (-1);
if (!nochdir)
(void)chdir("/");
if (!noclose && (fd = open(_PATH_DEVNULL, O_RDWR, 0)) != -1) {
(void)dup2(fd, STDIN_FILENO);
(void)dup2(fd, STDOUT_FILENO);
(void)dup2(fd, STDERR_FILENO);
if (fd > 2)
(void)close(fd);
}
return (0);
}
頭いいなあ。forkの使い方が冴えてる。こんな発想オイラーみたいな硬い頭では到底無理。
cronのmainの中から、deamonを呼んでるんで、どうやって親の内容をコピーするかと思ったら、forkして、親を子にcpしてる。そして、case 0: って事は、子だ。子が出来たら、親は殺しちゃうって言う戦国時代。
続く、ifからの部分では、子が動いているとな。後は、setsid()で、子が新しいセッションを作る。これでターミナルから独立するんだな。
そして、引数の指示により、トップdirに移動したり、標準入出力を闇に葬る操作をしてる。
トップdirに移動するのを推奨するのには訳が有る。(NFS)マウントされた先をワーキングdirしてダエモン君になっちゃうと、ダエモン君を停止してwdを変更しない限り、マウントを解除出来なくなっちゃうんだ。これは困るって事で、安全な場所 / に移動するのさ。
daemon君への変身の理屈が分かった所で、早速応用したい所がある。
それは、前回作った、休憩タイマー。
プチ不満があったんだ。その1は、起動時にバックグラウンドに追いやる必要がある。 その2は、タイマーを簡単に停止出来ちゃう事。fg して、Ctl-C で停止しちゃうからね。 (それが便利だと言えば、それまでですが)
これらの不満は、daemon君化で解消出来る。daemon君は何もrootの特権じゃないよ。平民が使ったっていいはず。
んな訳で、C語で書いてみた。
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/wait.h>
int main(){
int rv;
printf("pid= %d\n", getpid());
daemon(0, 1);
sleep(60);
if (fork() == 0){
execl("/bin/sh", "sh", "-c", "banner break | wall", NULL);
}
printf("PID= %d\n", getpid());
wait(&rv);
return(rv);
}
printfを挟んでいるのは、親が子に殺される、下克上を確かめる為です。また、daemon関数の第二引数が1になってるのは、標準入出力を生かしておくためです。(生きていないと、2番目のprintfの結果が闇に消えてしまう)
看板を出すのに、パイプが必要なんだけど、そんなの自前でやってると何時完成するか分からないので、shにお願いしちゃいました。(/bin/shの引数を実行内容にしてる。)
今回初めて、execlを使ったけど、第二引数以降は、argvの内容を文字列で指定するのね。最後のNULLをお忘れなく。
ob6$ ./a.out
pid= 52588
ob6$ PID= 98647
Broadcast Message from sakae@ob6.localdomain
(/dev/ttyp1) at 16:21 ...
##### ##### ###### ## # #
# # # # # # # # #
##### # # ##### # # ####
# # ##### # ###### # # 8
# # # # # # # # #
##### # # ###### # # # #
バックグラウンドに追いやる操作をしてないのに、自分で裏側に回りました。そして、shellのプロンプトが直ぐに表示されてます。 内容はスクリプトで書いたのと同様です。これだけ看板が出てくれば、さすがに休憩時って気が付くだろう。
ob6$ ps awxl| egrep '(a.out|UID)' UID PID PPID CPU PRI NI VSZ RSS WCHAN STAT TT TIME COMMAND 1000 98647 1 0 10 0 308 496 nanosle Is ?? 0:00.00 ./a.out 1000 33459 292 0 28 0 108 280 - R+/0 p2 0:00.00 egrep (a.out|UID)
ダエモン君はinitの支配下に入るのか。そしてWCHANがnanosleになってるって事は、タイマーの事象待ちになってますって事だな。そしてSTATのIは、20秒以上の待ちに入ってます。sは、セッションリーダーでございますって事だな。
ダエモン君は、制御端末が無いので達磨さんです。証拠は、端末名が表示される所が??に なってる事です。このダエモンを殺すには、面倒でもkillコマンドを実行する必要が有ります。
CentOS get source
赤帽さんを入れたので、ソースの取得と鑑賞はどうやるか調べてみた。
[sakae@cent hoge]$ sudo yumdownloader --source at : (1/3): updates-source/7/primary_db | 93 kB 00:00 (2/3): extras-source/7/primary_db | 51 kB 00:01 (3/3): base-source/7/primary_db | 1.0 MB 00:04 at-3.1.13-23.el7.src.rpm | 148 kB 00:02
専用コマンドが有るのね。取得はrootで実行する必要が有る。
[sakae@cent hoge]$ rpm -ivh at-3.1.13-23.el7.src.rpm
取得したものは、一般ユーザー権限で展開出来る。~/rpmbuild/ が作られてその中のSOURCEに ソースが入る。オリジナルへのパッチ情報がわーと有って、tar玉が(無い事も)展開されない形で置いてある。一手間かけて、展開ぐらいしておいてくれても良いと思うぞ。
今回はatのソースを取ってきたので、atd(ダエモン君)が有るか興味のある所。 atd.cが有った。cronとは独立してんのね。atd(8)によると、オプションでforegroundで動かしたりdebugメッセージをstderrに出せたり出来る充実ぶり。
ダエモン君に変身する方法は、daemon.cにまとめられていた。
daemon_setup()
:
if (!daemon_debug) {
close(0);
close(1);
close(2);
if ((open("/dev/null", O_RDWR) != 0) ||
(open("/dev/null", O_RDWR) != 1) ||
(open("/dev/null", O_RDWR) != 2)) {
perr("Error redirecting I/O");
}
}
if (daemon_foreground)
pid = getpid();
else {
pid = fork();
if (pid == -1) {
perr("Cannot fork");
} else if (pid != 0) {
exit(0);
}
(void) setsid();
}
大体deamon()と同じ事をしてる。だったら何故そちらを使わない? ちゃんとglibcには実装されているようだけど。推測するに、このdaemonは標準になっていないって事かな。 daemon(8)に
CONFORMING TO
Not in POSIX.1-2001. A similar function appears on the BSDs. The dae‐
mon() function first appeared in 4.4BSD.
なんて書いてあったからね。BSDライセンスは嫌いって表明でしょうか? それとも、このdaemon.cは、1996年に書かれていて、そこ頃は こんな便利なやつが無かった。動いているなら、そのままでいいじゃん。下手に触るなのリナス閣下の 教えに忠実に従ってるからかな。
あれ? TopDirへ移動するのが省かれているようだけど、運用で逃げているのかしら?