2017/11/08


追記

CreationFlags に直接指定出来ますね...

package main

import (
    "log"
    "os/exec"
    "syscall"
)

const (
    _IDLE_PRIORITY_CLASS = 0x40
)

func main() {
    cmd := exec.Command("notepad")
    cmd.SysProcAttr = &syscall.SysProcAttr{
        CreationFlags: syscall.CREATE_UNICODE_ENVIRONMENT | _IDLE_PRIORITY_CLASS,
    }
    err := cmd.Start()
    if err != nil {
        log.Fatal(err)
    }
    cmd.Wait()
}

追記ここまで

先日 GoCon に参加した際、牛乳を吹いてるアイコンの人に「Golang で優先度を変えてプロセスを起動するにはどうしたらいいんでしょうね」というお題を貰ったので書いてみました。

syscall には必要な API しか持ってないので API を持ってくるところが若干めんどくさいですが、出来る事は出来ます。プロセスをサスペンド状態で起動しておき、優先度を変更後にリジュームします。(起動後いきなり激しい動きをするヤバい奴がいない事もない)

package main

import (
    "log"
    "os/exec"
    "syscall"
    "unsafe"
)

const (
    _CREATE_SUSPENDED        = 0x00000004
    _IDLE_PRIORITY_CLASS     = 0x40
    _PROCESS_SET_INFORMATION = 0x0200
)

var (
    kernel32             = syscall.NewLazyDLL("kernel32")
    procSetPriorityClass = kernel32.NewProc("SetPriorityClass")
    procOpenThread       = kernel32.NewProc("OpenThread")
    procResumeThread     = kernel32.NewProc("ResumeThread")
    procThread32First    = kernel32.NewProc("Thread32First")
    procThread32Next     = kernel32.NewProc("Thread32Next")
)

func resumeChildThread(childpid interror {
    snapshot, err := syscall.CreateToolhelp32Snapshot(syscall.TH32CS_SNAPTHREAD, 0)
    if err != nil {
        return err
    }
    defer syscall.CloseHandle(snapshot)

    const _THREAD_SUSPEND_RESUME = 0x0002

    type ThreadEntry32 struct {
        Size           uint32
        tUsage         uint32
        ThreadID       uint32
        OwnerProcessID uint32
        BasePri        int32
        DeltaPri       int32
        Flags          uint32
    }

    var te ThreadEntry32
    te.Size = uint32(unsafe.Sizeof(te))
    ret, _, err := procThread32First.Call(uintptr(snapshot), uintptr(unsafe.Pointer(&te)))
    if ret == 0 {
        return err
    }
    for te.OwnerProcessID != uint32(childpid) {
        ret, _, err = procThread32Next.Call(uintptr(snapshot), uintptr(unsafe.Pointer(&te)))
        if ret == 0 {
            return err
        }
    }
    h, _, err := procOpenThread.Call(_THREAD_SUSPEND_RESUME, 1uintptr(te.ThreadID))
    if h == 0 {
        return err
    }
    defer syscall.Close(syscall.Handle(h))

    ret, _, err = procResumeThread.Call(h)
    if ret == 0xffffffff {
        return err
    }
    return nil
}

func main() {
    cmd := exec.Command("notepad")
    cmd.SysProcAttr = &syscall.SysProcAttr{
        CreationFlags: syscall.CREATE_UNICODE_ENVIRONMENT | _CREATE_SUSPENDED,
    }
    err := cmd.Start()
    if err != nil {
        log.Fatal(err)
    }

    hProcess, err := syscall.OpenProcess(_PROCESS_SET_INFORMATION, trueuint32(cmd.Process.Pid))
    if err != nil {
        log.Fatal(err)
    }
    r1, _, err := procSetPriorityClass.Call(uintptr(hProcess), uintptr(_IDLE_PRIORITY_CLASS))
    if r1 == 0 {
        log.Fatal(err)
    }
    syscall.CloseHandle(hProcess)

    resumeChildThread(cmd.Process.Pid)
    cmd.Wait()
}
Posted at by



2017/10/26


printf デバッグは便利だ。技術の後退と言われようと printf でないと解決できない事はまだまだたくさんあります。

今日は net/http でクライアントが得たレスポンスの JSON を確認したいといった場合に、どうデバッグしたらいいかを書いてみたいと思う。

Go のインタフェースは大よそ io.Reader もしくは io.Writer を使う様に設計されている。こうする事でプログラムがメモリを一度に沢山確保してしまわない様にしています。

package main

import (
    "encoding/json"
    "fmt"
    "log"
    "net/http"
)

type Foo struct {
    ID  string `json:"id"`
    Content string `json:"content"`
}

func main() {
    resp, err := http.Get("http://example.com")
    if err != nil {
        log.Fatal(err)
    }
    defer resp.Body.Close()

    var foo Foo
    err = json.NewDecoder(resp.Body).Decode(&foo)
    if err != nil {
        log.Fatal(err)
    }

    fmt.Println(foo.Content)
}

例えばこういうコードの、resp.Body に何が流れているのか確認したい場合、デバッグ出力する為に一旦 ioutil.ReadAll で全て読み取ったりしていないでしょうか。

package main

import (
    "bytes"
    "encoding/json"
    "fmt"
    "io/ioutil"
    "log"
    "net/http"
)

type Foo struct {
    ID  string `json:"id"`
    Content string `json:"content"`
}

func main() {
    resp, err := http.Get("http://example.com")
    if err != nil {
        log.Fatal(err)
    }
    defer resp.Body.Close()

    b, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        log.Fatal(err)
    }

    fmt.Println(string(b))

    var foo Foo
    err = json.NewDecoder(bytes.NewReader(b)).Decode(&foo)
    if err != nil {
        log.Fatal(err)
    }

    fmt.Println(foo.Content)
}

デバッグ表示したいだけなのに、ちょっとコードが増えてしまった感じがしますよね。デバッグを無効にしたいときに消すコードも多い。しかも json.NewDecoder の部分にも手を入れてしまわないといけなくてなんだか嫌な感じもします。元のコードは json.NewDecoder の箇所に手を入れられるから良いですが、時には io.Reader を引数に持つ関数に渡す必要があったり、ioutil.ReadAll で全て読み取る事が出来ないストリームデータの場合には使えません。こういった場合は io.TeeReader を使います。

package main

import (
    "encoding/json"
    "fmt"
    "io"
    "log"
    "net/http"
    "os"
)

type Foo struct {
    ID  string `json:"id"`
    Content string `json:"content"`
}

func main() {
    resp, err := http.Get("http://example.com")
    if err != nil {
        log.Fatal(err)
    }
    defer resp.Body.Close()

    var r io.Reader = resp.Body
    r = io.TeeReader(r, os.Stderr)

    var foo Foo
    err = json.NewDecoder(r).Decode(&foo)
    if err != nil {
        log.Fatal(err)
    }

    fmt.Println(foo.Content)
}

こうしておき、必要に応じて r = io.TeeReader(r, os.Stderr) の行をコメントアウトすれば良いのです。コメントアウトを外せばデバッグ表示になります。メモリも節約出来てお得感ありますね。

Posted at by



2017/10/24


たぶん逆引きが無いから探せないのかなと思ったので path/filepath にどういう機能があるのか書いておく。

パスからファイル名を得る

filepath.Base を使う。

package main

import (
    "path/filepath"
)

func main() {
    println(filepath.Base("C:/foo/bar"))
}

この場合 bar が表示される。

パスからディレクトリ名を得る

filepath.Dir を使う。

package main

import (
    "path/filepath"
)

func main() {
    println(filepath.Dir(filepath.Clean(`../foo\bar`)))
}

この場合、..\foo が表示される。

パスからボリューム名を得る

filepath.VolumeName を使う。

package main

import (
    "path/filepath"
)

func main() {
    println(filepath.VolumeName(`c:/windows/notepad.exe`))
}

この場合 c: が表示される。UNIX の場合は空文字列が返る。

相対パスから絶対パスに変換する

filepath.Abs を使う。

package main

import (
    "log"
    "path/filepath"
)

func main() {
    p, err := filepath.Abs("./testdata")
    if err != nil {
        log.Fatal(err)
    }
    println(p)
}

絶対パスから相対パスに変換する

filepath.Rel を使う。

package main

import (
    "log"
    "os"
    "path/filepath"
)

func main() {
    cwd, err := os.Getwd()
    if err != nil {
        log.Fatal(err)
    }
    p, err := filepath.Rel(cwd, `c:/dev`)
    if err != nil {
        log.Fatal(err)
    }
    println(p)
}

ディレクトリ配下であれば、それ以下の部分が。ディレクトリ配下でなければ .. で上昇した結果が返る。

パスを綺麗にする

../foo\bar\baz といった汚いパスを綺麗にするには filepath.Clean を使う。

package main

import (
    "path/filepath"
)

func main() {
    println(filepath.Clean(`../foo\bar`))
}

この場合、..\foo\bar が表示される。

シンボリックリンクのリンク元を得る

filepath.EvalSymlinks を使う。

package main

import (
    "log"
    "path/filepath"
)

func main() {
    p, err := filepath.EvalSymlinks(`c:/dirlink`)
    if err != nil {
        log.Fatal(err)
    }
    println(p)
}

Windows でも動作する。(ショートカットファイルではなくジャンクション)

パスから拡張子を得る

filepath.Ext を使う。

package main

import (
    "path/filepath"
)

func main() {
    println(filepath.Ext(`C:\Windows\Notepad.exe`))
}

.bashrc の様にドットで始まるファイル名を渡すと、ファイル名のまま返る。

スラッシュで区切られたパスを OS のパスセパレータに直す

filepath.FromSlash を使う。僕が path/filepath で一番好きな関数。

package main

import (
    "path/filepath"
)

func main() {
    println(filepath.FromSlash(`c:/users/mattn/.bashrc`))
}

この場合 c:\users\mattn\.bashrc が表示される。UNIX では何もしていない。Windows だけスラッシュがバックスラッシュに変換される。なので例えばファイルパスから URL のパスを作る時にこれを使ってくれると Windows ユーザが幸せになれる。

OS のパスセパレータで区切られたパスをスラッシュに直す

filepath.ToSlash を使う。filepath.FromSlash の逆。

package main

import (
    "path/filepath"
)

func main() {
    println(filepath.ToSlash(`c:\users\mattn\.bashrc`))
}

c:/users/mattn/.bashrc が表示される。

ファイルをマスクで検索する

filepath.Glob を使う。

package main

import (
    "log"
    "path/filepath"
)

func main() {
    files, err := filepath.Glob(`c:\Windows\*`)
    if err != nil {
        log.Fatal(err)
    }
    for _, f := range files {
        println(f)
    }
}

Windows の場合、バックスラッシュはエスケープ文字として扱われない。また ** は使えない。使いたい場合は zglob を使う。

パスの先頭に特定のディレクトリが含まれるか確認する

filepath.HasPrefix を使う。

package main

import (
    "path/filepath"
)

func main() {
    println(filepath.HasPrefix(`c:\Windows\Notepad.exe``c:\windows`))
}

気を付けないといけないのは、この動作は strings.HasPrefix でしかない事。この関数は deprecated として扱われている。

パスが絶対パスかを確認する

filepath.IsAbs を使う。

package main

import (
    "path/filepath"
)

func main() {
    println(filepath.IsAbs(`..\Notepad.exe`))
}

パスを結合する

filepath.Join を使う。

package main

import (
    "path/filepath"
)

func main() {
    println(filepath.Join(`c:\windows``system32``drivers``etc``hosts`))
}

OS のパスセパレータで結合される。可変個引数なので複数渡せる。

パスをディレクトリ名とファイル名に分解する

filepath.Split を使う。

package main

import (
    "path/filepath"
)

func main() {
    dir, filename := filepath.Split(`c:\windows\notepad.exe`)
    println(dir, filename)
}

c:\windows\notepad.exe に分けられる。

パスリストを分解する

PATH 環境変数の様に OS のパスリストセパレータで結合された物を分解する。filepath.SplitList を使う。

package main

import (
    "os"
    "path/filepath"
)

func main() {
    for _, p := range filepath.SplitList(os.Getenv("PATH")) {
        println(p)
    }
}

パスがパターンにマッチするか確認する

filepath.Match を使う。Glob が中で使っている物に過ぎない。

package main

import (
    "log"
    "path/filepath"
)

func main() {
    ok, err := filepath.Match(`*.exe``c:/windows/notepad.exe`)
    if err != nil {
        log.Fatal(err)
    }
    println(ok)
}

ディレクトリを下ってファイルを探索する

filepath.Walk を使う。例えば特定パス配下のディレクトリだけを探すのであれば以下の様に実行する。

package main

import (
    "log"
    "os"
    "path/filepath"
)

func main() {
    root := `c:\windows\system32\drivers\`
    err := filepath.Walk(root, func(p string, info os.FileInfo, err errorerror {
        if info.IsDir()  {
            println(p)
        }
        return nil
    })
    if err != nil {
        log.Fatal(err)
    }
}

探索を中断するには関数内でエラーを返す。もし特定のディレクトリ配下の探索をやめたいのであれば filepath.SkipDir を return で返してあげる。

なお物理ファイルの操作に path/filepath ではなく path を使うと爆発します。ちなみに、なぜここまで口をすっぱく言っているのかと言うと、UNIX で実装した物を Windows に持ってくると動かないからです。それどころかセキュリティ issue にもなり得る。

package main

import (
    "io"
    "net/http"
    "os"
    "path"
)

func main() {
    cwd, _ := os.Getwd()

    http.HandleFunc("/"func(w http.ResponseWriter, r *http.Request) {
        if ok, err := path.Match("/download/*", r.URL.Path); err != nil || !ok {
            http.NotFound(w, r)
            return
        }
        name := path.Join(cwd, r.URL.Path)
        f, err := os.Open(name)
        if err != nil {
            http.NotFound(w, r)
            return
        }
        defer f.Close()
        io.Copy(w, f)
    })
    http.ListenAndServe(":8080"nil)
}

何かをダウンロードさせるのにこういったコードを書いてしまうと、以下の様なリクエストでディレクトリトラバーサルが発生する。(正しくは http.ServeFile を使ってね)

http://localhost:8080/download/..%5cmain.go

これは Go のライブラリが悪い訳じゃない。こんなコードを書いた人が悪い。この辺は「みんなの Go 言語」にも書かれている。

みんなのGo言語[現場で使える実践テクニック] みんなのGo言語[現場で使える実践テクニック]
松木雅幸, mattn, 藤原俊一郎, 中島大一, 牧大輔, 鈴木健太
技術評論社 Kindle版 / ¥2,178 (2016年09月09日)
 
発送可能時間:

Posted at by