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入門

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版にするには、下記が参考になるかな。

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

赤帽さんを入れたので、ソースの取得と鑑賞はどうやるか調べてみた。

Rebuild a Source RPM

RPMについて

[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へ移動するのが省かれているようだけど、運用で逃げているのかしら?