Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 14289] ファイルの概要

このコミットは、Go言語のsyscallパッケージにLinuxの拡張属性(Extended Attributes, xattr)を操作するためのシステムコールを追加するものです。具体的には、ファイルの拡張属性の取得、設定、一覧表示、削除を行うGetxattr, Setxattr, Listxattr, Removexattrの各関数が導入されました。これにより、GoプログラムからLinuxファイルシステムの拡張属性を直接操作できるようになります。

コミット

commit 4094c1bae7cb6ace9311e560623bc0eb80bae91e
Author: Han-Wen Nienhuys <hanwen@google.com>
Date:   Thu Nov 1 14:49:38 2012 -0400

    syscall: add {Get,Set,List,Remove}xattr on Linux.
    
    R=golang-dev, minux.ma, fullung, dave, rsc, hanwenn
    CC=golang-dev
    https://golang.org/cl/6350074

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/4094c1bae7cb6ace9311e560623bc0eb80bae91e

元コミット内容

syscall: add {Get,Set,List,Remove}xattr on Linux.

このコミットは、Linux上で拡張属性(xattr)を操作するためのGetxattr, Setxattr, Listxattr, Removexattrシステムコールをsyscallパッケージに追加します。

変更の背景

Linuxのファイルシステムには、標準的なファイル属性(パーミッション、所有者、タイムスタンプなど)に加えて、任意のメタデータをファイルやディレクトリに関連付けることができる「拡張属性(Extended Attributes, xattr)」という機能があります。これは、アクセス制御リスト(ACLs)、セキュリティラベル(SELinuxなど)、ファイルチェックサム、カスタムタグなど、様々な目的で利用されます。

Go言語のsyscallパッケージは、オペレーティングシステムの低レベルな機能、特にシステムコールへのインターフェースを提供します。このコミット以前は、Goプログラムから直接Linuxの拡張属性を操作する標準的な方法が提供されていませんでした。そのため、Goでファイルシステムのより高度な機能を活用したり、特定のセキュリティ要件を満たすアプリケーションを開発したりする際に、この機能の欠如が制約となっていました。

この変更の背景には、Go言語がシステムプログラミングやインフラストラクチャツール開発においてより強力な選択肢となるよう、Linuxカーネルが提供する重要な機能へのアクセスを拡充するという意図があります。拡張属性のサポートを追加することで、Goプログラムはファイルシステムレベルでのより詳細な制御と情報管理が可能になり、より堅牢で機能豊富なアプリケーションを構築できるようになります。

前提知識の解説

1. Linux拡張属性 (Extended Attributes, xattr)

Linuxの拡張属性は、ファイルやディレクトリに名前と値のペア形式で追加のメタデータを保存する機能です。これは、通常のファイルデータとは別に保存され、ファイルシステムによってサポートされます。主な特徴は以下の通りです。

  • 名前空間 (Namespaces): 拡張属性は通常、user., system., security., trusted.といった名前空間でプレフィックスされます。
    • user.*: ユーザーが自由に設定できる属性。通常のファイルパーミッションに従います。
    • system.*: カーネルがACLsなどのシステムオブジェクトに使用します。
    • security.*: SELinuxなどのセキュリティモジュールが使用します。
    • trusted.*: CAP_SYS_ADMIN権限を持つプロセスのみがアクセスできる属性。
  • 用途: ACLsの実装、SELinuxなどの強制アクセス制御、ファイルチェックサムの保存、カスタムタグ付けなど。
  • システムコール: Linuxカーネルは、拡張属性を操作するための以下のシステムコールを提供しています。
    • setxattr(2): 属性を設定または更新。
    • getxattr(2): 属性の値を取得。
    • listxattr(2): 属性名のリストを取得。
    • removexattr(2): 属性を削除。 これらには、シンボリックリンク自体を操作するl*xattrや、ファイルディスクリプタを操作するf*xattrのバリエーションも存在します。

2. Go言語の syscall パッケージ

syscallパッケージは、Goプログラムからオペレーティングシステムの低レベルなプリミティブ関数(システムコール)にアクセスするためのインターフェースを提供します。このパッケージは、OS固有の定数、データ構造、およびシステムコールを直接呼び出すための関数を含んでいます。

  • システムコール: ユーザー空間のプログラムがカーネルにサービスを要求するためのメカニズムです。例えば、ファイルの読み書き、プロセスの作成、ネットワーク通信などがシステムコールを通じて行われます。
  • //sys および //sysnb ディレクティブ: syscallパッケージのGoソースファイル(例: syscall_linux.go)には、//sys//sysnbといった特殊なコメントディレクティブが記述されています。これらはgo generateツールによって解釈され、実際のシステムコールを呼び出すためのアーキテクチャ固有のGo関数(例: zsyscall_linux_amd64.go内の関数)が自動生成されます。
    • //sys: 通常のシステムコール。エラーハンドリングや引数の変換など、より複雑な処理を伴う場合があります。
    • //sysnb: "no blocking" の略で、ブロックしないシステムコール。通常、引数が少なく、単純なラッパーで済む場合に用いられます。

3. unsafe.Pointer

Go言語のunsafeパッケージは、型安全性をバイパスしてメモリを直接操作するための機能を提供します。unsafe.Pointerは、任意の型のポインタを保持できる特殊なポインタ型で、C言語のvoid*に似ています。システムコールを呼び出す際には、Goのバイトスライスや文字列をC言語のポインタとしてカーネルに渡す必要があるため、unsafe.Pointerが頻繁に利用されます。

技術的詳細

このコミットは、Linuxの拡張属性を操作するための4つの主要なシステムコール(Getxattr, Setxattr, Listxattr, Removexattr)をGoのsyscallパッケージに追加します。

  1. syscall_linux.goの変更:

    • このファイルは、Linuxシステムコールに対するGoのインターフェースを定義する中心的なファイルです。
    • //sysディレクティブを使用して、新しいxattr関連のシステムコールのGo関数シグネチャが追加されています。このディレクティブは、go generateツールがアーキテクチャ固有のzsyscall_linux_*.goファイルを生成する際に使用されます。
    • 追加された行は以下の通りです。
      • //sys Getxattr(path string, attr string, dest []byte) (sz int, err error)
      • //sys Listxattr(path string, dest []byte) (sz int, err error)
      • //sys Removexattr(path string, attr string) (err error)
      • //sys Setxattr(path string, attr string, data []byte, flags int) (err error)
    • 既存のコメントアウトされた(おそらく未実装または不要になった)xattr関連のシステムコール定義が削除されています。これは、新しい実装がそれらを置き換えるためです。
  2. zsyscall_linux_386.go, zsyscall_linux_amd64.go, zsyscall_linux_arm.goの変更:

    • これらのファイルは、syscall_linux.go内の//sysディレクティブに基づいてgo generateによって自動生成される、アーキテクチャ固有のシステムコールラッパーを含んでいます。
    • コミットでは、各アーキテクチャ(386, amd64, arm)向けに、Getxattr, Setxattr, Listxattr, Removexattrの実際のGo関数実装が追加されています。
    • これらの関数は、Goの文字列やバイトスライスをCスタイルのポインタに変換し、SyscallまたはSyscall6関数(Goの低レベルなシステムコール呼び出し関数)を使用して対応するLinuxシステムコールを呼び出します。
    • unsafe.Pointerが、Goのバイトスライス([]byte)の先頭アドレスをシステムコールに渡すために使用されています。例えば、destdataスライスが空の場合に_zero(ゼロ値のバイトへのポインタ)を使用することで、ヌルポインタの代わりに有効なアドレスを渡すようにしています。
    • システムコールの戻り値(レジスタr0, r1など)とエラーコード(e1)をGoの戻り値(sz, err)に適切にマッピングしています。

この変更により、Go開発者はsyscallパッケージを介して、Linuxの拡張属性を直接、かつGoの型システムに適合した形で操作できるようになりました。これは、Goが提供するOSとの低レベルなインタラクション能力をさらに拡張するものです。

コアとなるコードの変更箇所

src/pkg/syscall/syscall_linux.go

--- a/src/pkg/syscall/syscall_linux.go
+++ b/src/pkg/syscall/syscall_linux.go
@@ -849,6 +849,7 @@ func Mount(source string, target string, fstype string, flags uintptr, data stri
 //sysnb	Getppid() (ppid int)
 //sysnb	Getrusage(who int, rusage *Rusage) (err error)
 //sysnb	Gettid() (tid int)
+//sys	Getxattr(path string, attr string, dest []byte) (sz int, err error)
 //sys	InotifyAddWatch(fd int, pathname string, mask uint32) (watchdesc int, err error)
 //sysnb	InotifyInit() (fd int, err error)
 //sysnb	InotifyInit1(flags int) (fd int, err error)
@@ -856,6 +857,7 @@ func Mount(source string, target string, fstype string, flags uintptr, data stri
 //sysnb	Kill(pid int, sig Signal) (err error)
 //sys	Klogctl(typ int, buf []byte) (n int, err error) = SYS_SYSLOG
 //sys	Link(oldpath string, newpath string) (err error)
+//sys	Listxattr(path string, dest []byte) (sz int, err error)
 //sys	Mkdir(path string, mode uint32) (err error)
 //sys	Mkdirat(dirfd int, path string, mode uint32) (err error)
 //sys	Mknod(path string, mode uint32, dev int) (err error)
@@ -866,6 +868,7 @@ func Mount(source string, target string, fstype string, flags uintptr, data stri
 //sysnb prlimit(pid int, resource int, old *Rlimit, newlimit *Rlimit) (err error) = SYS_PRLIMIT64
 //sys	read(fd int, p []byte) (n int, err error)
 //sys	Readlink(path string, buf []byte) (n int, err error)
+//sys	Removexattr(path string, attr string) (err error)
 //sys	Rename(oldpath string, newpath string) (err error)
 //sys	Renameat(olddirfd int, oldpath string, newdirfd int, newpath string) (err error)
 //sys	Rmdir(path string) (err error)
@@ -875,6 +878,7 @@ func Mount(source string, target string, fstype string, flags uintptr, data stri
 //sysnb	Setsid() (pid int, err error)
 //sysnb	Settimeofday(tv *Timeval) (err error)
 //sysnb	Setuid(uid int) (err error)
+//sys	Setxattr(path string, attr string, data []byte, flags int) (err error)
 //sys	Symlink(oldpath string, newpath string) (err error)
 //sys	Sync()
 //sysnb	Sysinfo(info *Sysinfo_t) (err error)
@@ -954,7 +958,6 @@ func Munmap(b []byte) (err error) {
 // Getitimer
 // Getpmsg
 // Getpriority
-// Getxattr
 // IoCancel
 // IoDestroy
 // IoGetevents
@@ -966,7 +969,6 @@ func Munmap(b []byte) (err error) {
 // KexecLoad
 // Keyctl
 // Lgetxattr
-// Listxattr
 // Llistxattr
 // LookupDcookie
 // Lremovexattr
@@ -1004,7 +1006,6 @@ func Munmap(b []byte) (err error) {
 // Readahead
 // Readv
 // RemapFilePages
-// Removexattr
 // RequestKey
 // RestartSyscall
 // RtSigaction
@@ -1033,7 +1034,6 @@ func Munmap(b []byte) (err error) {
 // SetThreadArea
 // SetTidAddress
 // Setpriority
-// Setxattr
 // Shmat
 // Shmctl
 // Shmdt
@@ -1059,5 +1059,4 @@ func Munmap(b []byte) (err error) {
 // Vmsplice
 // Vserver
 // Waitid
-// Writev
 // _Sysctl

src/pkg/syscall/zsyscall_linux_386.go (amd64, armも同様のパターン)

--- a/src/pkg/syscall/zsyscall_linux_386.go
+++ b/src/pkg/syscall/zsyscall_linux_386.go
@@ -527,6 +527,33 @@ func Gettid() (tid int) {
 
 // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
 
+func Getxattr(path string, attr string, dest []byte) (sz int, err error) {
+	var _p0 *byte
+	_p0, err = BytePtrFromString(path)
+	if err != nil {
+		return
+	}
+	var _p1 *byte
+	_p1, err = BytePtrFromString(attr)
+	if err != nil {
+		return
+	}
+	var _p2 unsafe.Pointer
+	if len(dest) > 0 {
+		_p2 = unsafe.Pointer(&dest[0])
+	} else {
+		_p2 = unsafe.Pointer(&_zero)
+	}
+	r0, _, e1 := Syscall6(SYS_GETXATTR, uintptr(unsafe.Pointer(_p0)), uintptr(unsafe.Pointer(_p1)), uintptr(_p2), uintptr(len(dest)), 0, 0)
+	sz = int(r0)
+	if e1 != 0 {
+		err = e1
+	}
+	return
+}
+
+// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
+
 func InotifyAddWatch(fd int, pathname string, mask uint32) (watchdesc int, err error) {
 	var _p0 *byte
 	_p0, err = BytePtrFromString(pathname)
@@ -623,6 +650,28 @@ func Link(oldpath string, newpath string) (err error) {
 
 // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
 
+func Listxattr(path string, dest []byte) (sz int, err error) {
+	var _p0 *byte
+	_p0, err = BytePtrFromString(path)
+	if err != nil {
+		return
+	}
+	var _p1 unsafe.Pointer
+	if len(dest) > 0 {
+		_p1 = unsafe.Pointer(&dest[0])
+	} else {
+		_p1 = unsafe.Pointer(&_zero)
+	}
+	r0, _, e1 := Syscall(SYS_LISTXATTR, uintptr(unsafe.Pointer(_p0)), uintptr(_p1), uintptr(len(dest)))
+	sz = int(r0)
+	if e1 != 0 {
+		err = e1
+	}
+	return
+}
+
+// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
+
 func Mkdir(path string, mode uint32) (err error) {
 	var _p0 *byte
 	_p0, err = BytePtrFromString(path)
@@ -772,6 +821,26 @@ func Readlink(path string, buf []byte) (n int, err error) {
 
 // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
 
+func Removexattr(path string, attr string) (err error) {
+	var _p0 *byte
+	_p0, err = BytePtrFromString(path)
+	if err != nil {
+		return
+	}
+	var _p1 *byte
+	_p1, err = BytePtrFromString(attr)
+	if err != nil {
+		return
+	}
+	_, _, e1 := Syscall(SYS_REMOVEXATTR, uintptr(unsafe.Pointer(_p0)), uintptr(unsafe.Pointer(_p1)), 0)
+	if e1 != 0 {
+		err = e1
+	}
+	return
+}
+
+// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
+
 func Rename(oldpath string, newpath string) (err error) {
 	var _p0 *byte
 	_p0, err = BytePtrFromString(oldpath)
@@ -900,6 +969,32 @@ func Setuid(uid int) (err error) {
 
 // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
 
+func Setxattr(path string, attr string, data []byte, flags int) (err error) {
+	var _p0 *byte
+	_p0, err = BytePtrFromString(path)
+	if err != nil {
+		return
+	}
+	var _p1 *byte
+	_p1, err = BytePtrFromString(attr)
+	if err != nil {
+		return
+	}
+	var _p2 unsafe.Pointer
+	if len(data) > 0 {
+		_p2 = unsafe.Pointer(&data[0])
+	} else {
+		_p2 = unsafe.Pointer(&_zero)
+	}
+	_, _, e1 := Syscall6(SYS_SETXATTR, uintptr(unsafe.Pointer(_p0)), uintptr(unsafe.Pointer(_p1)), uintptr(_p2), uintptr(len(data)), uintptr(flags), 0)
+	if e1 != 0 {
+		err = e1
+	}
+	return
+}
+
+// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
+
 func Symlink(oldpath string, newpath string) (err error) {
 	var _p0 *byte
 	_p0, err = BytePtrFromString(oldpath)

コアとなるコードの解説

このコミットの核心は、GoのsyscallパッケージがLinuxの拡張属性システムコールを呼び出すためのラッパー関数を提供することです。

  1. syscall_linux.goにおける//sysディレクティブ:

    • //sys Getxattr(path string, attr string, dest []byte) (sz int, err error):
      • path: 拡張属性を取得するファイルのパス。
      • attr: 取得する拡張属性の名前。
      • dest: 取得した属性値を格納するバイトスライス。
      • sz: 取得した属性値のサイズ(バイト数)。
      • err: エラー情報。
    • //sys Listxattr(path string, dest []byte) (sz int, err error):
      • path: 拡張属性名を一覧表示するファイルのパス。
      • dest: 取得した属性名(ヌル終端された文字列のリスト)を格納するバイトスライス。
      • sz: 取得した属性名の合計サイズ。
      • err: エラー情報。
    • //sys Removexattr(path string, attr string) (err error):
      • path: 拡張属性を削除するファイルのパス。
      • attr: 削除する拡張属性の名前。
      • err: エラー情報。
    • //sys Setxattr(path string, attr string, data []byte, flags int) (err error):
      • path: 拡張属性を設定するファイルのパス。
      • attr: 設定する拡張属性の名前。
      • data: 設定する属性値のバイトスライス。
      • flags: 属性設定の動作を制御するフラグ(例: XATTR_CREATEで新規作成のみ、XATTR_REPLACEで既存属性の置き換えのみ)。
      • err: エラー情報。

    これらのディレクティブは、Goの関数シグネチャを定義し、go generateツールに対して、対応するLinuxシステムコールを呼び出すための低レベルなGoコードを生成するように指示します。

  2. zsyscall_linux_*.goにおける生成された関数:

    • これらのファイル(例: zsyscall_linux_386.go)には、上記の//sysディレクティブに基づいて生成された実際のシステムコールラッパー関数が含まれています。
    • 各関数は、Goの文字列(path, attr)をCスタイルのヌル終端バイトポインタ(_p0, _p1)に変換するためにBytePtrFromStringを使用します。
    • バイトスライス(dest, data)は、その先頭アドレスをunsafe.Pointerを使って取得し、システムコールに渡されます。スライスが空の場合には、_zeroというゼロ値のバイトへのポインタが使用され、有効なアドレスが常に渡されるように工夫されています。
    • SyscallまたはSyscall6関数が、実際のLinuxシステムコールを呼び出します。これらの関数は、システムコール番号(例: SYS_GETXATTR)と引数をuintptr型で受け取ります。
    • システムコールからの戻り値(通常はレジスタr0, r1)とエラーコード(e1)が、Goの関数の戻り値(sz, err)に変換されます。e1 != 0の場合、システムコールがエラーを返したことを意味し、Goのエラー型に変換されます。

このメカニズムにより、Go開発者はGoの慣用的な方法でファイルシステムの拡張属性を操作できるようになり、Goプログラムの機能性が向上しました。

関連リンク

参考にした情報源リンク

  • Web検索結果: "linux extended attributes xattr system calls"
    • 特に、Linux extended attributes (xattrs) の概要、システムコール、およびコマンドラインツールに関する情報源。
    • man7.orgのシステムコールマニュアルページ。
    • wikipedia.orgの拡張属性に関する記事。
    • ibm.comの拡張属性に関する記事。
    • archlinux.orgの拡張属性に関する記事。
    • linux-audit.comの拡張属性に関する記事。
    • github.ioの拡張属性に関する記事。
    • unix.comの拡張属性に関する記事。
    • lesbonscomptes.comの拡張属性に関する記事。
    • linuxcampus.netの拡張属性に関する記事。