more golang

『立っている者は親でも使え』、これおばあちゃんの教えだった。最近の幼稚園は、この教えを園児達に伝授してるのかね?

幼稚園の前を通りかかったその時、檻もとえ柵の中から遊戯に使うカラーリングが転げ出て来た。黄色い声が聞こえた。おじちゃん、その輪取ってよと。

あいよ、と渡してあげると、おじちゃんありがとう。なかなか教育が行き届いているな。そう思っていたら、坊やが飛んできて、そこに落ちてる、松ぼっくりを取ってよとリクエスト。 はいよと渡すと、今度は違う子が、イチョウの葉っぱを取ってよとリクエスト。

全く人使いの荒い餓鬼供だ。もとえ、子供は地域の宝です。大切にしましょう。

みんな、名前何て言うの。返ってきた答えはキラキラネームばかり。

おっと、園児の名前なんか聞いてたら、誘拐予備軍と間違えられるぞ。桑原桑原。

『我と来て遊べや、ほったらかしの園児達』保母さんは、園児同士の抗争の仲裁で大わらわでしたよ。

tour

golangのツアーが出来る。但しオンライン版だ。ネットが隔絶した時もやりたい。だったら、ローカルマシンにインストールしちゃえ。

$ go get golang.org/x/tour

これで、$GOPATH/bin/tour って、バイナリーが出来て、いつでも起動が可能となる。よかったね。でもないんだな。localhostからしかアクセス出来ない設定になってるんだ。何故か?

任意のコードを実行出来ちゃうので、そんなのネットに公開出来ないよってのが理由。それを無視するにはどうする? ソースが有るんで、3999をkeyに検索してみろ。

local.go

var (
    httpListen  = flag.String("http", "x.x.x.x:3999", "host:port to listen on")
    openBrowser = flag.Bool("openbrowser", true, "open browser automatically")
)

このx.x.x.xを自IPにして、go get golang.org/x/tourすればOK。

cent:~$ go/bin/tour
2018/11/04 06:40:09 Serving content from /home/sakae/go/src/golang.org/x/tour
2018/11/04 06:40:09
WARNING!  WARNING!  WARNING!

I appear to be listening on an address that is not localhost.
Anyone with access to this address and port will have access
to this machine as the user running gotour.

If you don't understand this message, hit Control-C to terminate this process.

WARNING!  WARNING!  WARNING!

警告!警告!って、五月蝿いぐらい警告してるよ。理解出来ない人は、使うのやめれって、大人の対応を求めている。

2018/11/04 06:40:09 A browser window should open. If not, please visit http://x.x.x.x:3999

それはそうと、ブラウザーが勝手に開くはずなんだけど、そこら辺どーなってるの? 手がかりの片鱗が、上のcodeに出てるな。

// startBrowser tries to open the URL in a browser, and returns
// whether it succeed.
func startBrowser(url string) bool {
        // try to start the browser
        var args []string
        switch runtime.GOOS {
        case "darwin":
                args = []string{"open"}
        case "windows":
                args = []string{"cmd", "/c", "start"}
        default:
                args = []string{"xdg-open"}
        }
        cmd := exec.Command(args[0], append(args[1:], url)...)
        return cmd.Start() == nil
}

ふーん、GUI環境で機能するのね。

tag-jump for golang

Goプログラミングの環境構築 を参考に、ソースの海を飛び回れるようにした。なんたって、付属のソースを見るのが一番ですからね。tag-jumpするだけなら、godefだけで良いみたいだけど、気が変わってコードを書くようになったら、補完したいので、入れておいた。

% go get github.com/nsf/gocode
% go get github.com/rogpeppe/godef
(with-eval-after-load 'go-mode
    (define-key go-mode-map (kbd "M-.") 'godef-jump)
    (define-key go-mode-map (kbd "M-,") 'pop-tag-mark))

このemacs-lisp片をCentOS7に適用すると、シンボルが無いと怒られた。yumから入れたemacsが古いの? 以前もなんかemacsがらみで問題起こしたな。どんなやつが入ってる? emacs-24.3だった。時代がかったやつだな。この際新しいのにしておくか。

yum install ncurses-devel しとく。それから最新の26.1を取ってくる。

./configure --without-x 
  :
configure: error: The following required libraries were not found:
     gnutls
Maybe some development libraries/packages are missing?
If you don't want to link with them give
     --with-gnutls=no

gnutls関係者を探してみた。

cent:emacs-26.1$ yum provides gnutls-devel
  :
gnutls-devel-3.3.26-9.el7.i686 : Development files for the gnutls package
Repo        : base

そしてgnutls-develを入れてから、再度やり直し。 なんたって普段はGUI上で操作しないから、Xってそんなの関係ねぇでいいんです。古いやつはバッサリ切り捨てる選択もあるけど、一応温存。

alias e='/usr/local/bin/emacsclient -nw -a ""'
alias ekill='emacsclient -e "(kill-emacs)"'
alias emacs='emacs-26.1'

って、bashに指示しといた。GUIからあげると、この指示は自動的に無視されるはずなんで、大目にみてね。

emacsclient

Emacs emacsclientを何気なく使いつつclientで開いた地点をdefault directoryにするも参考に。

こんな技が紹介されてた。が、オイラーの所では機能しなかった。コピペしただけなんで、どう動いているか未確認。ちゃんとちゃんとのコピペしましょ。

愛リアスで、環境変数に現在値(pwd)を設定。それから、emacsclientを起動するとな。 emacsのinitファイルでは、環境変数を読んで、その値をdefault-directoryに設定するための関数を定義。find-fileとかが呼ばれたら、それをフックして、定義した関数を挟み込むとな。

何故機能しない? 分解して調べてみる。怪しそうなのは、getenv関数だな。説明読むか。C-hf getenv

getenv is an interactive compiled Lisp function in `env.el'.

(getenv VARIABLE &optional FRAME)

Get the value of environment variable VARIABLE.
VARIABLE should be a string.  Value is nil if VARIABLE is undefined in
the environment.  Otherwise, value is a string.

If optional parameter FRAME is non-nil, then it should be a
frame.  This function will look up VARIABLE in its `environment'
parameter.

Otherwise, this function searches `process-environment' for
VARIABLE.  If it is not found there, then it continues the search
in the environment list of the selected frame.

どうやらキャッシュしたものを返しているみたい。

process-environment is a variable defined in `C source code'.
Its value is
("OLDPWD=/home/sakae/go/src" "_=/usr/local/bin/emacs-26.1" ...)

だったらと思って、暫く格闘のための、aliasを捻り出す。

alias e='emacsclient -a "" -e "(setq default-directory  \"`pwd`\" )"  -nw'
#alias e='emacsclient -a "" -e "(setenv \"EMACSPWD\" \"`pwd`\" )" -e "(set-default-pwd)" -nw'
#alias e='export EMACSPWD=`pwd`; /usr/local/bin/emacsclient -nw -a ""'

どれも動かん。

Reload environment variables こういう悩みを訴えている方もいた。

オイラーが欲しいのは、diredした時に、emacsclientを立ち上げたpwdが欲しいんだ。 独自の関数を書かないと駄目かな? その前に、lib-src/emacsclient.c でも見て桶。

alias e='emacsclient -a "" -e "(setenv \"EMACSPWD\" \"`pwd`\" )" -nw'
(defun my-dired ()
  (interactive)
    (dired (setq default-directory (getenv "EMACSPWD"))))
(define-key global-map (kbd "C-c d") 'my-dired)

一つ(使い込むと不具合が色々露呈されるかも)問題がある。e hoge.txtとかしちゃうと、おかしくなる。dired経由で使え。(自分的には、許容範囲。emacsclientは、閲覧ツールですから)

nvi in CentOS

御多聞に漏れず、viはvimになってる。重いじゃん。黒い画面に青字ってのが目に毒だ。リナは、本当に余計な色使いをしてくれて、困ったものだ。ええい、由緒正しいものにしちゃえ。

The Berkeley Vi Editor Home Pageから頂いてきた、nviを使う事にする。普通にconfigすると、登録されていないヘッダーを要求されて、ヘッダー地獄に陥るので、それを避けよう。

build/config.h

/* Define if you have the System V style pty calls. */
//#define HAVE_SYS5_PTY 1

出来たnviを/usr/local/bin/nviに移動させておいた。

sudo yum erase `rpm -qa | grep vim`

これで、vimを綺麗さっぱり消してしまおうかな。(vimも嫌われたものだ)

この所ちょいと出番の無いウブでnviをコンパイルしてみた。ええ、わざわざ自分で作って入れるまでもなく、とっくに鎮座してますけどね。ウブは遊び人用のOS。CentOSは職業人用OSです。守備範囲が違う事は重々承知してますけど。

何の問題もなくコンパイル出来た。CentOSに無かったのは、下記ヘッダー。ウブでは普通に入っていた。基本部分なんだけどな。CentOSはそこまで進化していないって結論だな。

libc6-dev: /usr/include/x86_64-linux-gnu/sys/stropts.h

色々寄り道したけど、本線に復帰する。

dlv debug

golang用のデバッガーでdlvってのが有る。これを裸で使ってもいいんだけど、配色が気に要らないとか、使い心地が悪い。これはもう、emacsから使うしかないな。そのためには、go-dlv っていうemacs用のパッケージを入れてあげる。

Golangのデバッガdelveの使い方は、素直な使い方ね。

普通にemacsを起動してから、 M-x dlv する。dlv debug まで聞いてくるんで、対象を指定。引数が有る場合は、-- を前置してから、セットする。 ( -- で、分離するのが味噌です。)

下記は、前回のオレオレ証明書作成現場の内偵例です。

Run dlv (like this): dlv debug generate_cert.go -- --host localhost

少々待たされるけど、画面が反転したら、ブレークポイントをセットして、コンテニュー。 後は、gdbみたいに操作すれば良い。迷ったらhelpね。

(dlv) b main.main
Breakpoint 1 set at 0x5522ab for main.main() ./generate_cert.go:66
(dlv) c
n
(dlv) n
> main.main() ./generate_cert.go:69 (PC: 0x5522c7)
    64: }
    65:
    66: func main() {
    67:         flag.Parse()
    68:
=>  69:         if len(*host) == 0 {
    70:                 log.Fatalf("Missing required --host parameter")
    71:         }
    72:
    73:         var priv interface{}
    74:         var err error
(dlv) p host
*"localhost"

引数がちゃんと取り込まれている。焦って、ここで set host = "hogefuga" とかしても、この変数は書き換え不可って怒られるぞ。

(dlv) bt
0  0x00000000005542df in main.main
   at ./generate_cert.go:73
1  0x000000000042f815 in runtime.main
   at /usr/local/go/src/runtime/proc.go:201
2  0x000000000045b3a1 in runtime.goexit
   at /usr/local/go/src/runtime/asm_amd64.s:1333

また、ソースを見てて、ムラムラしてきたら、 関数にカーソルを合わせて、 M-x dlv-current-func RET すると、dlvが走って、関数の所で止まるって言う、荒技もあるぞ。何かの時のために、覚えておくと良い鴨。

example golang

Go by Example

golang.jp

シュッと golang に入門する話

インタフェースの実装パターン

Go言語におけるポリモーフィズム

お気楽 Go 言語プログラミング入門

Goならわかるシステムプログラミング

golintのソースを読んでGoの書き方を学ぶ

sample webdav

この間githubで事故が有って、復旧まで大分時間がかかったよう。ソースをそこに預けている人は、思わぬ休暇に万歳した事でしょう。いや、納期に迫られて冷や汗タラタラかな。一社集中は困るんです。有名所のソフトは、tar玉にしてgithubとは関係ない所に保管しておいて欲しいな。ましてや、親がM$になった現実を考えると尚更ですよ。

んな訳で、 前回出て来たwebdavサーバーの安全装置を外した簡略版を全く別なオイラーのサイトに保管しておく。

package main

import (
        "flag"
        "fmt"
        "log"
        "net/http"
        "golang.org/x/net/webdav"
)

var dir string

func main() {
        dirFlag := flag.String("d", "./", "htdocs, default is CWD")
        httpPort := flag.Int("p", 8282, "Port to serve on (Plain HTTP)")
        flag.Parse()
	
        dir = *dirFlag
        srv := &webdav.Handler{
                FileSystem: webdav.Dir(dir),
                LockSystem: webdav.NewMemLS(),
                Logger: func(r *http.Request, err error) {
                        log.Printf("WEBDAV [%s]: %s \n", r.Method, r.URL)
                        },
                }
        http.Handle("/", srv)
        err := http.ListenAndServe(fmt.Sprintf(":%d", *httpPort), nil)
        if err != nil {
                log.Fatalf("Error with WebDAV server: %v", err)
        }
}

こうしてみると、構造が良く見える。これを起点にtag-jumpして潜って行きたい。

webdav.Handlerにカーソルを合わせて、tag-jumpしようとするも

godef: no declaration found for webdav.Handler

こんな文句を言われ、出鼻を挫かれた。それならば、http.Handleはどうよ?

// Handle registers the handler for the given pattern
// in the DefaultServeMux.
// The documentation for ServeMux explains how patterns are matched.
func Handle(pattern string, handler Handler) { DefaultServeMux.Handle(pattern, handler) }

server.goへとご案内されたぞ。この違いは何かと言うと、先の奴は構造体、後者は関数。それで差を付けてるって事?

webdav.Handlerって、ちゃんとwebdav.goに定義されてるんだけどな。

type Handler struct {
        // Prefix is the URL path prefix to strip from WebDAV resource paths.
        Prefix string
        // FileSystem is the virtual file system.
        FileSystem FileSystem
        // LockSystem is the lock management system.
        LockSystem LockSystem
        // Logger is an optional error logger. If non-nil, it will be called
        // for all HTTP requests.
        Logger func(*http.Request, error)
}

構造体と言うか型は、扱えないのかな。楽しさ半減だな。だったら、あれしか無いよ。 三種の神器、editor,compiler,gebuggerのうち、debuggerに頼ろう。

dlv attach

dlvのコマンド解説を見てると、さりげなく

  attach      Attach to running process and begin debugging.

これ、gdbでもお世話になる大好きな機能。よもやdlvにも有るとは。いや有って当然。

attachしてdebugするって手法は、カーネルで特定のシステムコールを捕まえるのに多用する技。Webサーバーも、接続を待ち構えていて、接続が有った時にdebuggerが起動して欲しい。 こういう時に、attachは打って付け。webdavサーバーで同様な事をする。

あらかじめwebdavのPIDを調べておき、それを引数にdlvを起動する。

sakae@usvr:/tmp/top$ dlv attach 3372
Could not attach to pid 3372: this could be caused by a kernel security setting, try writing "0" to /proc/sys/kernel/yama/ptrace_scope

と、変な横槍が入ったぞ。ウブの厳格さの表れか?職業OSだとどうなる事やら。まずは、目先の障害を乗り越えるんだ。

sakae@usvr:/tmp/top$ sudo sh -c 'echo 0 > /proc/sys/kernel/yama/ptrace_scope'
sakae@usvr:/tmp/top$ dlv attach 3372
Type 'help' for list of commands.

指示の通り、指定された変数に0を書き込んであげる。そして、dlvを再実行。今度は文句を言われる事なく起動した。

(dlv) b webdav.ServeHTTP
Breakpoint 1 set at 0x6575b3 for golang.org/x/net/webdav.(*Handler).ServeHTTP()
/home/sakae/go/src/golang.org/x/net/webdav/webdav.go:42
(dlv) c
> golang.org/x/net/webdav.(*Handler).ServeHTTP() /home/sakae/go/src/golang.org/x/net/webdav/webdav.go:42 (hits goroutine(4):1 total:1) (PC: 0x6575b3)
Warning: debugging optimized function
    37:                 return r, http.StatusOK, nil
    38:         }
    39:         return p, http.StatusNotFound, errPrefixMismatch
    40: }
    41:
=>  42: func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    43:         status, err := http.StatusBadRequest, errUnsupportedMethod
    44:         if h.FileSystem == nil {
    45:                 status, err = http.StatusInternalServerError, errNoFileSystem
    46:         } else if h.LockSystem == nil {
    47:                 status, err = http.StatusInternalServerError, errNoLockSystem

webdav.goの中心の所にBPを置いて継続。別端末から、cadaverでwebdavサーバーに接続を試みる。おおちゃんとdebuggerが起きてきた。

(dlv) bt
0  0x00000000006575b3 in golang.org/x/net/webdav.(*Handler).ServeHTTP
   at /home/sakae/go/src/golang.org/x/net/webdav/webdav.go:42
1  0x0000000000605287 in net/http.(*ServeMux).ServeHTTP
   at /usr/local/go/src/net/http/server.go:2361
2  0x0000000000605cbb in net/http.serverHandler.ServeHTTP
   at /usr/local/go/src/net/http/server.go:2741
3  0x0000000000602846 in net/http.(*conn).serve
   at /usr/local/go/src/net/http/server.go:1847
4  0x0000000000457cb1 in runtime.goexit
   at /usr/local/go/src/runtime/asm_amd64.s:1333

どんな経路で、ここに至ったかが、ソースを読む道筋を教えてくれている。

(dlv) p r
*net/http.Request {
        Method: "PROPFIND",
        URL: *net/url.URL {
                Scheme: "",
                  :
                Fragment: "",},
        Proto: "HTTP/1.1",
        ProtoMajor: 1,
        ProtoMinor: 1,
        Header: net/http.Header [
                "User-Agent": [
                        "cadaver/0.23.3 neon/0.30.2",
                ],
          :
        TransferEncoding: []string len: 0, cap: 0, nil,
        Close: false,
        Host: "localhost:8282",
        Form: net/url.Values nil,
        PostForm: net/url.Values nil,
        MultipartForm: *mime/multipart.Form nil,
        Trailer: net/http.Header nil,
        RemoteAddr: "[::1]:45608",
         :

リクエストヘッダーも、簡単に見られるな。

(dlv) bt
0  0x0000000000459cb0 in runtime.epollwait
   at /usr/local/go/src/runtime/sys_linux_amd64.s:671
1  0x00000000004293b2 in runtime.netpoll
   at /usr/local/go/src/runtime/netpoll_epoll.go:71
2  0x0000000000432a7c in runtime.findrunnable
   at /usr/local/go/src/runtime/proc.go:2469
3  0x000000000043354a in runtime.schedule
   at /usr/local/go/src/runtime/proc.go:2613
4  0x0000000000433f32 in runtime.goexit0
   at /usr/local/go/src/runtime/proc.go:2790
5  0x0000000000455bbb in runtime.mcall
   at /usr/local/go/src/runtime/asm_amd64.s:299

サーバー側が、何処で息を潜めているかも一目瞭然。手掛かり十分。

dlv trace

dlvにもう一つ、オイラーの興味を引く機能が搭載されてた。traceですって。traceと言えば、Schemeの骨格を仕様化してるRnRSで必須とされてる手続きだな。面白そうなので、喰ってみる。

dlv trace --help

Trace program execution.

The trace sub command will set a tracepoint on every function matching the
provided regular expression and output information when tracepoint is hit.

This is useful if you do not want to begin an entire debug session, but merely
want to know what functions your process is executing.

Usage:
  dlv trace [package] regexp [flags]
  :

早速、オレオレ証明書に適用してみる。

sakae@usvr:/tmp$ dlv trace generate_cert.go main -- --host hoge.fuga.com
> runtime.main() /usr/local/go/src/runtime/proc.go:110 (hits goroutine(1):1 total:1) (PC: 0x42f663)
Warning: debugging optimized function
> runtime.main.func1() /usr/local/go/src/runtime/proc.go:130 (hits goroutine(1):1 total:1) (PC: 0x4581af)
Warning: debugging optimized function
> main.init() <autogenerated>:1 (hits goroutine(1):1 total:1) (PC: 0x554323)
> main.main() ./generate_cert.go:66 (hits goroutine(1):1 total:1) (PC: 0x5522ab)
> main.publicKey(interface {}(*crypto/rsa.PrivateKey) 0xc0000af298, interface {} nil) ./generate_cert.go:39 (hits goroutine(1):1 total:1) (PC: 0x551bc0)
2018/11/07 15:38:53 wrote cert.pem
> main.pemBlockForKey(interface {}(*crypto/rsa.PrivateKey) 0xc0001b9298, (*encoding/pem.Block)(0x241)) ./generate_cert.go:50 (hits goroutine(1):1 total:1) (PC: 0x551d3b)
2018/11/07 15:38:53 wrote key.pem
Process 3542 has exited with status 0

main.mainが呼ばれるまでの前処理が良く分かる。前処理ってのは、C語のバイナリーで言うcrtnに相当するものだな。いや、もっと広く色々な事(直ぐに思い付くのはガベコレとか、ゴルーチンの制御)をやってくれる環境なんだな。

これなかなか良いな。何かの折にきっと役立つだろう。

etags

何気にemacs26.1のetags.cを見てたら、

static const char *Go_suffixes [] = {"go", NULL};
static const char Go_help [] =
  "In Go code, functions, interfaces and packages are tags.";

こんなのが出てた。

cent:webdav$ etags `find . -name '*.go' | grep -v _test.go`

/home/sakae/go/src/golang.org/x/net/webdavの所で、test用のコードを省いてTAGSを作ってみた。

そして試しにgodefでは検出出来なかったあれ(Handler)を検出出来るか試した。 M-x xref-find-definitions を使ってね。そしたら、ちゃんと飛んで行ったよ。 どんな物が登録されてるかTAGSを覗いてみると、

webdav.go,702
package webdav ^?6,219
type Handler ^?20,362
func (h *Handler) stripPrefix(^?32,740
func (h *Handler) ServeHTTP(^?42,1005
 :

ちゃんと、package,typeも見分けていた。あんたは偉い。manによると

       ble  in a format understood by vi(1).  Both forms of the program under-
       stand the syntax of C, Objective C, C++, Java, Fortran, Ada, Cobol, Er-
       lang,  Forth,  Go,  HTML, LaTeX, Emacs Lisp/Common Lisp, Lua, Makefile,
       Pascal, Perl, Ruby, PHP, PostScript, Python, Prolog,  Scheme  and  most
       assembler-like  syntaxes.   Both  forms read the files specified on the

こんなにサポートしてたよ。もうctagsは忘れてしまってもいいな。それ以前にオイラーには使い道が無いけどね。