2017/08/17

Recent entries from same category

  1. Golang の archive/zip でタイムゾーンの問題とファイル名の問題が解決した。
  2. Golang で優先度を変えてプロセスを起動する。
  3. net/http でレスポンスの内容を確認したいなら io.TeeReader を使おう
  4. Golang で物理ファイルの操作に path/filepath でなく path を使うと爆発します。
  5. gRPC のサービスが簡単に作れるライブラリ「lile」

https://play.golang.org/p/CHGYhtzRsK Go 1.8.3で『GOOS="darwin"』だと完走するけど、『GOOS=...

Go 1.8.3で『GOOS="darwin"』だと完走するけど、『GOOS="windows"』だと途中でエラーになるプログラム。


osやsyscallパッケージを使っている時、こういう事があって当然なのかパッケージの実装に問題があるのか判断する規準となりそうな記述って公式になにかありますでしょうか。

https://plus.google.com/u/0/103737163485109218200/posts/gfNS2Bz4QrH?cfem=1

UNIX だと以下のコードはパスするのに Windows だとエラーになるという話。

package main

import (
    "fmt"
    "os"
)

func chk(err error) {
    if err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }
}

func main() {
    f, err := os.Create("log.txt")
    chk(err)
    defer f.Close()
    err = os.Rename("log.txt""log.txt.bak")
    chk(err)

    os.Remove("log.txt")
    os.Remove("log.txt.bak")
}

UNIX の場合、ファイルを開いている最中にファイルを削除するとファイルシステム上からは unlink されるけど、実際は存在していてファイルの読み込みが出来ます。そして close(2) されたタイミングで削除されます。Windows の FILE_SHARE_DELETE は一見、UNIX のそれっぽく見えるのですが動作が異なります。

例えば test.txt というファイルを作り以下のコードを実行します。

#include <windows.h>
#include <stdio.h>

int
main(int argc, char* argv[]) {
  HANDLE h = CreateFile("test.txt",
      GENERIC_READ | GENERIC_WRITE,
      FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
      NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
  getchar();
  char buf[256] = {0};
  DWORD nread;
  printf("%d\n", ReadFile(h, buf, 256, &nread, NULL));
  printf("%s,%d\n", buf, nread);
  return 0;
}

getchar() で処理が停止している間に別のコマンドプロンプトでファイルを消してみて下さい。まずは実行

delete1

そして削除。ファイルが消えると思いきや残っています。そしてアクセスするとエラーになります。

delete2

Windows の FILE_SHARE_DELETE の動作は UNIX のそれとは異なるのです。例えばファイルを消した後で、ファイルが存在しないつもりで処理を続行してしまうと別のエラーが発生する事になります。

package main

import (
    "fmt"
    "os"
)

func chk(err error) {
    if err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }
}

func main() {
    f, err := os.Create("log.txt")
    chk(err)
    defer f.Close()
    f.Close()
    err = os.Rename("log.txt""log.txt.bak")
    chk(err)

    os.Remove("log.txt")
    os.Remove("log.txt.bak")
}

この様な OS 間の動作の違いを吸収する事は言語レベルでは出来ません。マルチプラットフォームで動作するコードを書きたいと思われるのであれば、os.Rename や os.Remove の前に Close する事をおすすめします。

ちなみに「みんなのGo言語」にも解説が書いてあります。

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


blog comments powered by Disqus