mirror of
https://github.com/moby/moby.git
synced 2026-01-11 18:51:37 +00:00
pkg/system: move to daemon/internal
It has no external users, and this package still has too many different responsibilities, some of which may be available elsewhere, so moving it internal so that we can decide to dismantle it further. Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
This commit is contained in:
@@ -1,48 +0,0 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"os"
|
||||
"syscall"
|
||||
"time"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// Used by Chtimes
|
||||
var unixEpochTime, unixMaxTime time.Time
|
||||
|
||||
func init() {
|
||||
unixEpochTime = time.Unix(0, 0)
|
||||
if unsafe.Sizeof(syscall.Timespec{}.Nsec) == 8 {
|
||||
// This is a 64 bit timespec
|
||||
// os.Chtimes limits time to the following
|
||||
//
|
||||
// Note that this intentionally sets nsec (not sec), which sets both sec
|
||||
// and nsec internally in time.Unix();
|
||||
// https://github.com/golang/go/blob/go1.19.2/src/time/time.go#L1364-L1380
|
||||
unixMaxTime = time.Unix(0, 1<<63-1)
|
||||
} else {
|
||||
// This is a 32 bit timespec
|
||||
unixMaxTime = time.Unix(1<<31-1, 0)
|
||||
}
|
||||
}
|
||||
|
||||
// Chtimes changes the access time and modified time of a file at the given path.
|
||||
// If the modified time is prior to the Unix Epoch (unixMinTime), or after the
|
||||
// end of Unix Time (unixEpochTime), os.Chtimes has undefined behavior. In this
|
||||
// case, Chtimes defaults to Unix Epoch, just in case.
|
||||
func Chtimes(name string, atime time.Time, mtime time.Time) error {
|
||||
if atime.Before(unixEpochTime) || atime.After(unixMaxTime) {
|
||||
atime = unixEpochTime
|
||||
}
|
||||
|
||||
if mtime.Before(unixEpochTime) || mtime.After(unixMaxTime) {
|
||||
mtime = unixEpochTime
|
||||
}
|
||||
|
||||
if err := os.Chtimes(name, atime, mtime); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Take platform specific action for setting create time.
|
||||
return setCTime(name, mtime)
|
||||
}
|
||||
@@ -1,110 +0,0 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
// TestChtimesATime tests Chtimes access time on a tempfile.
|
||||
func TestChtimesATime(t *testing.T) {
|
||||
file := filepath.Join(t.TempDir(), "exist")
|
||||
if err := os.WriteFile(file, []byte("hello"), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
beforeUnixEpochTime := unixEpochTime.Add(-100 * time.Second)
|
||||
afterUnixEpochTime := unixEpochTime.Add(100 * time.Second)
|
||||
|
||||
// Test both aTime and mTime set to Unix Epoch
|
||||
t.Run("both aTime and mTime set to Unix Epoch", func(t *testing.T) {
|
||||
if err := Chtimes(file, unixEpochTime, unixEpochTime); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
f, err := os.Stat(file)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
stat := f.Sys().(*syscall.Stat_t)
|
||||
aTime := time.Unix(stat.Atim.Unix())
|
||||
if aTime != unixEpochTime {
|
||||
t.Fatalf("Expected: %s, got: %s", unixEpochTime, aTime)
|
||||
}
|
||||
})
|
||||
|
||||
// Test aTime before Unix Epoch and mTime set to Unix Epoch
|
||||
t.Run("aTime before Unix Epoch and mTime set to Unix Epoch", func(t *testing.T) {
|
||||
if err := Chtimes(file, beforeUnixEpochTime, unixEpochTime); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
f, err := os.Stat(file)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
stat := f.Sys().(*syscall.Stat_t)
|
||||
aTime := time.Unix(stat.Atim.Unix())
|
||||
if aTime != unixEpochTime {
|
||||
t.Fatalf("Expected: %s, got: %s", unixEpochTime, aTime)
|
||||
}
|
||||
})
|
||||
|
||||
// Test aTime set to Unix Epoch and mTime before Unix Epoch
|
||||
t.Run("aTime set to Unix Epoch and mTime before Unix Epoch", func(t *testing.T) {
|
||||
if err := Chtimes(file, unixEpochTime, beforeUnixEpochTime); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
f, err := os.Stat(file)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
stat := f.Sys().(*syscall.Stat_t)
|
||||
aTime := time.Unix(stat.Atim.Unix())
|
||||
if aTime != unixEpochTime {
|
||||
t.Fatalf("Expected: %s, got: %s", unixEpochTime, aTime)
|
||||
}
|
||||
})
|
||||
|
||||
// Test both aTime and mTime set to after Unix Epoch (valid time)
|
||||
t.Run("both aTime and mTime set to after Unix Epoch (valid time)", func(t *testing.T) {
|
||||
if err := Chtimes(file, afterUnixEpochTime, afterUnixEpochTime); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
f, err := os.Stat(file)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
stat := f.Sys().(*syscall.Stat_t)
|
||||
aTime := time.Unix(stat.Atim.Unix())
|
||||
if aTime != afterUnixEpochTime {
|
||||
t.Fatalf("Expected: %s, got: %s", afterUnixEpochTime, aTime)
|
||||
}
|
||||
})
|
||||
|
||||
// Test both aTime and mTime set to Unix max time
|
||||
t.Run("both aTime and mTime set to Unix max time", func(t *testing.T) {
|
||||
if err := Chtimes(file, unixMaxTime, unixMaxTime); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
f, err := os.Stat(file)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
stat := f.Sys().(*syscall.Stat_t)
|
||||
aTime := time.Unix(stat.Atim.Unix())
|
||||
if aTime.Truncate(time.Second) != unixMaxTime.Truncate(time.Second) {
|
||||
t.Fatalf("Expected: %s, got: %s", unixMaxTime.Truncate(time.Second), aTime.Truncate(time.Second))
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
//go:build !windows
|
||||
|
||||
package system
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// setCTime will set the create time on a file. On Unix, the create
|
||||
// time is updated as a side effect of setting the modified time, so
|
||||
// no action is required.
|
||||
func setCTime(path string, ctime time.Time) error {
|
||||
return nil
|
||||
}
|
||||
@@ -1,100 +0,0 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
// TestChtimesModTime tests Chtimes on a tempfile. Test only mTime, because
|
||||
// aTime is OS dependent.
|
||||
func TestChtimesModTime(t *testing.T) {
|
||||
file := filepath.Join(t.TempDir(), "exist")
|
||||
if err := os.WriteFile(file, []byte("hello"), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
beforeUnixEpochTime := unixEpochTime.Add(-100 * time.Second)
|
||||
afterUnixEpochTime := unixEpochTime.Add(100 * time.Second)
|
||||
|
||||
// Test both aTime and mTime set to Unix Epoch
|
||||
t.Run("both aTime and mTime set to Unix Epoch", func(t *testing.T) {
|
||||
if err := Chtimes(file, unixEpochTime, unixEpochTime); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
f, err := os.Stat(file)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if f.ModTime() != unixEpochTime {
|
||||
t.Fatalf("Expected: %s, got: %s", unixEpochTime, f.ModTime())
|
||||
}
|
||||
})
|
||||
|
||||
// Test aTime before Unix Epoch and mTime set to Unix Epoch
|
||||
t.Run("aTime before Unix Epoch and mTime set to Unix Epoch", func(t *testing.T) {
|
||||
if err := Chtimes(file, beforeUnixEpochTime, unixEpochTime); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
f, err := os.Stat(file)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if f.ModTime() != unixEpochTime {
|
||||
t.Fatalf("Expected: %s, got: %s", unixEpochTime, f.ModTime())
|
||||
}
|
||||
})
|
||||
|
||||
// Test aTime set to Unix Epoch and mTime before Unix Epoch
|
||||
t.Run("aTime set to Unix Epoch and mTime before Unix Epoch", func(t *testing.T) {
|
||||
if err := Chtimes(file, unixEpochTime, beforeUnixEpochTime); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
f, err := os.Stat(file)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if f.ModTime() != unixEpochTime {
|
||||
t.Fatalf("Expected: %s, got: %s", unixEpochTime, f.ModTime())
|
||||
}
|
||||
})
|
||||
|
||||
// Test both aTime and mTime set to after Unix Epoch (valid time)
|
||||
t.Run("both aTime and mTime set to after Unix Epoch (valid time)", func(t *testing.T) {
|
||||
if err := Chtimes(file, afterUnixEpochTime, afterUnixEpochTime); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
f, err := os.Stat(file)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if f.ModTime() != afterUnixEpochTime {
|
||||
t.Fatalf("Expected: %s, got: %s", afterUnixEpochTime, f.ModTime())
|
||||
}
|
||||
})
|
||||
|
||||
// Test both aTime and mTime set to Unix max time
|
||||
t.Run("both aTime and mTime set to Unix max time", func(t *testing.T) {
|
||||
if err := Chtimes(file, unixMaxTime, unixMaxTime); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
f, err := os.Stat(file)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if f.ModTime().Truncate(time.Second) != unixMaxTime.Truncate(time.Second) {
|
||||
t.Fatalf("Expected: %s, got: %s", unixMaxTime.Truncate(time.Second), f.ModTime().Truncate(time.Second))
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
// setCTime will set the create time on a file. On Windows, this requires
|
||||
// calling SetFileTime and explicitly including the create time.
|
||||
func setCTime(path string, ctime time.Time) error {
|
||||
pathp, err := windows.UTF16PtrFromString(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
h, err := windows.CreateFile(pathp,
|
||||
windows.FILE_WRITE_ATTRIBUTES, windows.FILE_SHARE_WRITE, nil,
|
||||
windows.OPEN_EXISTING, windows.FILE_FLAG_BACKUP_SEMANTICS, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer windows.Close(h)
|
||||
c := windows.NsecToFiletime(ctime.UnixNano())
|
||||
return windows.SetFileTime(h, &c, nil, nil)
|
||||
}
|
||||
@@ -1,107 +0,0 @@
|
||||
//go:build windows
|
||||
|
||||
package system
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
// TestChtimesATimeWindows tests Chtimes access time on a tempfile on Windows.
|
||||
func TestChtimesATimeWindows(t *testing.T) {
|
||||
file := filepath.Join(t.TempDir(), "exist")
|
||||
if err := os.WriteFile(file, []byte("hello"), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
beforeUnixEpochTime := unixEpochTime.Add(-100 * time.Second)
|
||||
afterUnixEpochTime := unixEpochTime.Add(100 * time.Second)
|
||||
|
||||
// Test both aTime and mTime set to Unix Epoch
|
||||
t.Run("both aTime and mTime set to Unix Epoch", func(t *testing.T) {
|
||||
if err := Chtimes(file, unixEpochTime, unixEpochTime); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
f, err := os.Stat(file)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
aTime := time.Unix(0, f.Sys().(*syscall.Win32FileAttributeData).LastAccessTime.Nanoseconds())
|
||||
if aTime != unixEpochTime {
|
||||
t.Fatalf("Expected: %s, got: %s", unixEpochTime, aTime)
|
||||
}
|
||||
})
|
||||
|
||||
// Test aTime before Unix Epoch and mTime set to Unix Epoch
|
||||
t.Run("aTime before Unix Epoch and mTime set to Unix Epoch", func(t *testing.T) {
|
||||
if err := Chtimes(file, beforeUnixEpochTime, unixEpochTime); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
f, err := os.Stat(file)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
aTime := time.Unix(0, f.Sys().(*syscall.Win32FileAttributeData).LastAccessTime.Nanoseconds())
|
||||
if aTime != unixEpochTime {
|
||||
t.Fatalf("Expected: %s, got: %s", unixEpochTime, aTime)
|
||||
}
|
||||
})
|
||||
|
||||
// Test aTime set to Unix Epoch and mTime before Unix Epoch
|
||||
t.Run("aTime set to Unix Epoch and mTime before Unix Epoch", func(t *testing.T) {
|
||||
if err := Chtimes(file, unixEpochTime, beforeUnixEpochTime); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
f, err := os.Stat(file)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
aTime := time.Unix(0, f.Sys().(*syscall.Win32FileAttributeData).LastAccessTime.Nanoseconds())
|
||||
if aTime != unixEpochTime {
|
||||
t.Fatalf("Expected: %s, got: %s", unixEpochTime, aTime)
|
||||
}
|
||||
})
|
||||
|
||||
// Test both aTime and mTime set to after Unix Epoch (valid time)
|
||||
t.Run("both aTime and mTime set to after Unix Epoch (valid time)", func(t *testing.T) {
|
||||
if err := Chtimes(file, afterUnixEpochTime, afterUnixEpochTime); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
f, err := os.Stat(file)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
aTime := time.Unix(0, f.Sys().(*syscall.Win32FileAttributeData).LastAccessTime.Nanoseconds())
|
||||
if aTime != afterUnixEpochTime {
|
||||
t.Fatalf("Expected: %s, got: %s", afterUnixEpochTime, aTime)
|
||||
}
|
||||
})
|
||||
|
||||
// Test both aTime and mTime set to Unix max time
|
||||
t.Run("both aTime and mTime set to Unix max time", func(t *testing.T) {
|
||||
if err := Chtimes(file, unixMaxTime, unixMaxTime); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
f, err := os.Stat(file)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
aTime := time.Unix(0, f.Sys().(*syscall.Win32FileAttributeData).LastAccessTime.Nanoseconds())
|
||||
if aTime.Truncate(time.Second) != unixMaxTime.Truncate(time.Second) {
|
||||
t.Fatalf("Expected: %s, got: %s", unixMaxTime.Truncate(time.Second), aTime.Truncate(time.Second))
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -1,122 +0,0 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
// SddlAdministratorsLocalSystem is local administrators plus NT AUTHORITY\System.
|
||||
const SddlAdministratorsLocalSystem = "D:P(A;OICI;GA;;;BA)(A;OICI;GA;;;SY)"
|
||||
|
||||
// MkdirAllWithACL is a custom version of os.MkdirAll modified for use on Windows
|
||||
// so that it is both volume path aware, and can create a directory with
|
||||
// an appropriate SDDL defined ACL.
|
||||
func MkdirAllWithACL(path string, sddl string) error {
|
||||
sa, err := makeSecurityAttributes(sddl)
|
||||
if err != nil {
|
||||
return &os.PathError{Op: "mkdirall", Path: path, Err: err}
|
||||
}
|
||||
return mkdirAllWithACL(path, sa)
|
||||
}
|
||||
|
||||
// mkdirAllWithACL is a custom version of os.MkdirAll with DACL support on Windows.
|
||||
// It is fully identical to [os.MkdirAll] if no DACL is provided.
|
||||
//
|
||||
// Code in this function is based on the implementation in [go1.23.4].
|
||||
//
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
//
|
||||
// [go1.23.4]: https://github.com/golang/go/blob/go1.23.4/src/os/path.go#L12-L66
|
||||
func mkdirAllWithACL(path string, perm *windows.SecurityAttributes) error {
|
||||
if perm == nil {
|
||||
return os.MkdirAll(path, 0)
|
||||
}
|
||||
// Fast path: if we can tell whether path is a directory or file, stop with success or error.
|
||||
dir, err := os.Stat(path)
|
||||
if err == nil {
|
||||
if dir.IsDir() {
|
||||
return nil
|
||||
}
|
||||
return &os.PathError{Op: "mkdir", Path: path, Err: syscall.ENOTDIR}
|
||||
}
|
||||
|
||||
// Slow path: make sure parent exists and then call Mkdir for path.
|
||||
|
||||
// Extract the parent folder from path by first removing any trailing
|
||||
// path separator and then scanning backward until finding a path
|
||||
// separator or reaching the beginning of the string.
|
||||
i := len(path) - 1
|
||||
for i >= 0 && os.IsPathSeparator(path[i]) {
|
||||
i--
|
||||
}
|
||||
for i >= 0 && !os.IsPathSeparator(path[i]) {
|
||||
i--
|
||||
}
|
||||
if i < 0 {
|
||||
i = 0
|
||||
}
|
||||
|
||||
// If there is a parent directory, and it is not the volume name,
|
||||
// recurse to ensure parent directory exists.
|
||||
if parent := path[:i]; len(parent) > len(filepath.VolumeName(path)) {
|
||||
err = mkdirAllWithACL(parent, perm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Parent now exists; invoke Mkdir and use its result.
|
||||
err = mkdirWithACL(path, perm)
|
||||
if err != nil {
|
||||
// Handle arguments like "foo/." by
|
||||
// double-checking that directory doesn't exist.
|
||||
dir, err1 := os.Lstat(path)
|
||||
if err1 == nil && dir.IsDir() {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// mkdirWithACL creates a new directory. If there is an error, it will be of
|
||||
// type *PathError. .
|
||||
//
|
||||
// This is a modified and combined version of os.Mkdir and windows.Mkdir
|
||||
// in golang to cater for creating a directory am ACL permitting full
|
||||
// access, with inheritance, to any subfolder/file for Built-in Administrators
|
||||
// and Local System.
|
||||
func mkdirWithACL(name string, sa *windows.SecurityAttributes) error {
|
||||
if sa == nil {
|
||||
return os.Mkdir(name, 0)
|
||||
}
|
||||
|
||||
namep, err := windows.UTF16PtrFromString(name)
|
||||
if err != nil {
|
||||
return &os.PathError{Op: "mkdir", Path: name, Err: err}
|
||||
}
|
||||
|
||||
err = windows.CreateDirectory(namep, sa)
|
||||
if err != nil {
|
||||
return &os.PathError{Op: "mkdir", Path: name, Err: err}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func makeSecurityAttributes(sddl string) (*windows.SecurityAttributes, error) {
|
||||
var sa windows.SecurityAttributes
|
||||
sa.Length = uint32(unsafe.Sizeof(sa))
|
||||
sa.InheritHandle = 1
|
||||
var err error
|
||||
sa.SecurityDescriptor, err = windows.SecurityDescriptorFromString(sddl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &sa, nil
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
//go:build linux || freebsd
|
||||
|
||||
package system
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"syscall"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// LUtimesNano is used to change access and modification time of the specified path.
|
||||
// It's used for symbol link file because unix.UtimesNano doesn't support a NOFOLLOW flag atm.
|
||||
func LUtimesNano(path string, ts []syscall.Timespec) error {
|
||||
uts := []unix.Timespec{
|
||||
unix.NsecToTimespec(syscall.TimespecToNsec(ts[0])),
|
||||
unix.NsecToTimespec(syscall.TimespecToNsec(ts[1])),
|
||||
}
|
||||
err := unix.UtimesNanoAt(unix.AT_FDCWD, path, uts, unix.AT_SYMLINK_NOFOLLOW)
|
||||
if err != nil && !errors.Is(err, unix.ENOSYS) {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
//go:build linux || freebsd
|
||||
|
||||
package system
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// prepareFiles creates files for testing in the temp directory
|
||||
func prepareFiles(t *testing.T) (file, invalid, symlink string) {
|
||||
t.Helper()
|
||||
dir := t.TempDir()
|
||||
|
||||
file = filepath.Join(dir, "exist")
|
||||
if err := os.WriteFile(file, []byte("hello"), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
invalid = filepath.Join(dir, "doesnt-exist")
|
||||
symlink = filepath.Join(dir, "symlink")
|
||||
if err := os.Symlink(file, symlink); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
return file, invalid, symlink
|
||||
}
|
||||
|
||||
func TestLUtimesNano(t *testing.T) {
|
||||
file, invalid, symlink := prepareFiles(t)
|
||||
|
||||
before, err := os.Stat(file)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
ts := []syscall.Timespec{{Sec: 0, Nsec: 0}, {Sec: 0, Nsec: 0}}
|
||||
if err := LUtimesNano(symlink, ts); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
symlinkInfo, err := os.Lstat(symlink)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if before.ModTime().Unix() == symlinkInfo.ModTime().Unix() {
|
||||
t.Fatal("The modification time of the symlink should be different")
|
||||
}
|
||||
|
||||
fileInfo, err := os.Stat(file)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if before.ModTime().Unix() != fileInfo.ModTime().Unix() {
|
||||
t.Fatal("The modification time of the file should be same")
|
||||
}
|
||||
|
||||
if err := LUtimesNano(invalid, ts); err == nil {
|
||||
t.Fatal("Doesn't return an error on a non-existing file")
|
||||
}
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
type XattrError struct {
|
||||
Op string
|
||||
Attr string
|
||||
Path string
|
||||
Err error
|
||||
}
|
||||
|
||||
func (e *XattrError) Error() string { return e.Op + " " + e.Attr + " " + e.Path + ": " + e.Err.Error() }
|
||||
|
||||
func (e *XattrError) Unwrap() error { return e.Err }
|
||||
|
||||
// Timeout reports whether this error represents a timeout.
|
||||
func (e *XattrError) Timeout() bool {
|
||||
t, ok := e.Err.(interface{ Timeout() bool })
|
||||
return ok && t.Timeout()
|
||||
}
|
||||
|
||||
// Lgetxattr retrieves the value of the extended attribute identified by attr
|
||||
// and associated with the given path in the file system.
|
||||
// It returns a nil slice and nil error if the xattr is not set.
|
||||
func Lgetxattr(path string, attr string) ([]byte, error) {
|
||||
sysErr := func(err error) ([]byte, error) {
|
||||
return nil, &XattrError{Op: "lgetxattr", Attr: attr, Path: path, Err: err}
|
||||
}
|
||||
|
||||
// Start with a 128 length byte array
|
||||
dest := make([]byte, 128)
|
||||
sz, errno := unix.Lgetxattr(path, attr, dest)
|
||||
|
||||
for errors.Is(errno, unix.ERANGE) {
|
||||
// Buffer too small, use zero-sized buffer to get the actual size
|
||||
sz, errno = unix.Lgetxattr(path, attr, []byte{})
|
||||
if errno != nil {
|
||||
return sysErr(errno)
|
||||
}
|
||||
dest = make([]byte, sz)
|
||||
sz, errno = unix.Lgetxattr(path, attr, dest)
|
||||
}
|
||||
|
||||
switch {
|
||||
case errors.Is(errno, unix.ENODATA):
|
||||
return nil, nil
|
||||
case errno != nil:
|
||||
return sysErr(errno)
|
||||
}
|
||||
|
||||
return dest[:sz], nil
|
||||
}
|
||||
|
||||
// Lsetxattr sets the value of the extended attribute identified by attr
|
||||
// and associated with the given path in the file system.
|
||||
func Lsetxattr(path string, attr string, data []byte, flags int) error {
|
||||
err := unix.Lsetxattr(path, attr, data, flags)
|
||||
if err != nil {
|
||||
return &XattrError{Op: "lsetxattr", Attr: attr, Path: path, Err: err}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user