【Go】os.OpenFileのフラグについて

【Go】os.OpenFileのフラグについて

Hacker binary attack code. Made with Canon 5d Mark III and analog vintage lens, Leica APO Macro Elmarit-R 2.8 100mm (Year: 1993)
Photo by Markus Spiske / Unsplash

※go version go1.19 darwin/amd64

ファイルを作成するGoの関数os.OpenFileは、3つの引数を取ります。
https://pkg.go.dev/os#OpenFile

func OpenFile(name string, flag int, perm FileMode) (*File, error)

第2引数のflag intは以下のような値を入れられます

os.OpenFile("filename.txt", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)

この縦棒でつないでいるやつなんなんだろうと思い、調べてみました。

第2引数に入れられる値は以下のようなintの数値が定義されていました。

const (
  O_RDONLY int = syscall.O_RDONLY // open the file read-only.
  O_WRONLY int = syscall.O_WRONLY // open the file write-only.
  O_RDWR   int = syscall.O_RDWR   // open the file read-write.
  O_APPEND int = syscall.O_APPEND // append data to the file when writing.
  O_CREATE int = syscall.O_CREAT  // create a new file if none exists.
  O_EXCL   int = syscall.O_EXCL   // used with O_CREATE, file must not exist.
  O_SYNC   int = syscall.O_SYNC   // open for synchronous I/O.
  O_TRUNC  int = syscall.O_TRUNC  // truncate regular writable file when opened.
)

私の環境ではこのような値の数値でした

O_RDONLY int = 0
O_WRONLY int = 1
O_RDWR   int = 2
O_APPEND int = 8
O_CREATE int = 512
O_EXCL   int = 2048
O_SYNC   int = 128
O_TRUNC  int = 1024

「|」はORビット演算子らしく、intの値をORビット演算しているということでした!

各値を2進数で表すとこんな感じで、被り無く1がフラグのようになっているのがわかります(本来は64ビットなので、64桁になるように左に0が続く)

O_RDONLY int = 000000000000 // 0
O_WRONLY int = 000000000001 // 1
O_RDWR   int = 000000000010 // 2
O_APPEND int = 000000001000 // 8
O_CREATE int = 001000000000 // 512
O_EXCL   int = 100000000000 // 2048
O_SYNC   int = 000010000000 // 128
O_TRUNC  int = 010000000000 // 1024

すべての値をORビット演算子でつなぐと、「111010001011」こんな風な値になり、3723になるはず

package main

import (
	"fmt"
	"os"
)

func main() {
	// すべてのフラグをORビット演算子でつなぐ
	result := os.O_RDONLY | os.O_WRONLY | os.O_RDWR | os.O_APPEND | os.O_CREATE | os.O_EXCL | os.O_SYNC | os.O_TRUNC
	fmt.Println(result)

}

▼実行結果

$ go run main.go 
3723

playground:https://go.dev/play/p/D-p3-coCyNt
なる!

OpenFile関数も、フラグを「|」ではなくて「+」でつなげば動くんじゃないかな?

package main

import (
	"io"
	"os"
	"strings"
)

func main() {

	// 読み取り・書き込み、ファイルが存在しない場合は新規作成するのflagを足したものを引数に渡す
	file, err := os.OpenFile("test.txt", os.O_RDWR+os.O_CREATE, 0666)
	if err != nil {
		panic(err)
	}
	defer file.Close()

	io.Copy(file, strings.NewReader("テスト書き込み"))

	// 読み込み位置を先頭に戻す
	file.Seek(0, 0)

	// ファイルの内容を標準出力に表示
	io.Copy(os.Stdout, file)
}

▼実行結果

$ go run main.go 
テスト書き込み

playground:https://go.dev/play/p/0jO4FaRNX1K
できてる!

おわりに

ふと気になったORビット演算子ですが、調べていくとGoのintの桁が環境によって桁数が違うことや、それがPCの64bit版・32bit版のことで、それがレジスタのことを表していて、bitをフラグとして扱ってシステムコールに渡して、OSからCPUに指示を出して…と、なんとなーく覚えたり忘れたりしていた知識がつながったのがとても楽しい経験でした、おわり!