```
* use go 1.17 semantics in go.mod
* pin tonistiigi/fsutil dep to match docker/docker's dep
go get -u ./...
go mod vendor
```
This commit is contained in:
Jason Hall
2022-01-21 13:59:16 -05:00
committed by GitHub
parent a59970f4ac
commit 6bbd8a331c
3595 changed files with 429699 additions and 120494 deletions
+41 -26
View File
@@ -17,12 +17,11 @@
package fs
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"sync"
"github.com/pkg/errors"
)
var bufferPool = &sync.Pool{
@@ -39,6 +38,8 @@ type XAttrErrorHandler func(dst, src, xattrKey string, err error) error
type copyDirOpts struct {
xeh XAttrErrorHandler
// xex contains a set of xattrs to exclude when copying
xex map[string]struct{}
}
type CopyDirOpt func(*copyDirOpts) error
@@ -61,6 +62,19 @@ func WithAllowXAttrErrors() CopyDirOpt {
return WithXAttrErrorHandler(xeh)
}
// WithXAttrExclude allows for exclusion of specified xattr during CopyDir operation.
func WithXAttrExclude(keys ...string) CopyDirOpt {
return func(o *copyDirOpts) error {
if o.xex == nil {
o.xex = make(map[string]struct{}, len(keys))
}
for _, key := range keys {
o.xex[key] = struct{}{}
}
return nil
}
}
// CopyDir copies the directory from src to dst.
// Most efficient copy of files is attempted.
func CopyDir(dst, src string, opts ...CopyDirOpt) error {
@@ -77,35 +91,35 @@ func CopyDir(dst, src string, opts ...CopyDirOpt) error {
func copyDirectory(dst, src string, inodes map[uint64]string, o *copyDirOpts) error {
stat, err := os.Stat(src)
if err != nil {
return errors.Wrapf(err, "failed to stat %s", src)
return fmt.Errorf("failed to stat %s: %w", src, err)
}
if !stat.IsDir() {
return errors.Errorf("source %s is not directory", src)
return fmt.Errorf("source %s is not directory", src)
}
if st, err := os.Stat(dst); err != nil {
if err := os.Mkdir(dst, stat.Mode()); err != nil {
return errors.Wrapf(err, "failed to mkdir %s", dst)
return fmt.Errorf("failed to mkdir %s: %w", dst, err)
}
} else if !st.IsDir() {
return errors.Errorf("cannot copy to non-directory: %s", dst)
return fmt.Errorf("cannot copy to non-directory: %s", dst)
} else {
if err := os.Chmod(dst, stat.Mode()); err != nil {
return errors.Wrapf(err, "failed to chmod on %s", dst)
return fmt.Errorf("failed to chmod on %s: %w", dst, err)
}
}
fis, err := ioutil.ReadDir(src)
if err != nil {
return errors.Wrapf(err, "failed to read %s", src)
return fmt.Errorf("failed to read %s: %w", src, err)
}
if err := copyFileInfo(stat, dst); err != nil {
return errors.Wrapf(err, "failed to copy file info for %s", dst)
if err := copyFileInfo(stat, src, dst); err != nil {
return fmt.Errorf("failed to copy file info for %s: %w", dst, err)
}
if err := copyXAttrs(dst, src, o.xeh); err != nil {
return errors.Wrap(err, "failed to copy xattrs")
if err := copyXAttrs(dst, src, o.xex, o.xeh); err != nil {
return fmt.Errorf("failed to copy xattrs: %w", err)
}
for _, fi := range fis {
@@ -121,37 +135,38 @@ func copyDirectory(dst, src string, inodes map[uint64]string, o *copyDirOpts) er
case (fi.Mode() & os.ModeType) == 0:
link, err := getLinkSource(target, fi, inodes)
if err != nil {
return errors.Wrap(err, "failed to get hardlink")
return fmt.Errorf("failed to get hardlink: %w", err)
}
if link != "" {
if err := os.Link(link, target); err != nil {
return errors.Wrap(err, "failed to create hard link")
return fmt.Errorf("failed to create hard link: %w", err)
}
} else if err := CopyFile(target, source); err != nil {
return errors.Wrap(err, "failed to copy files")
return fmt.Errorf("failed to copy files: %w", err)
}
case (fi.Mode() & os.ModeSymlink) == os.ModeSymlink:
link, err := os.Readlink(source)
if err != nil {
return errors.Wrapf(err, "failed to read link: %s", source)
return fmt.Errorf("failed to read link: %s: %w", source, err)
}
if err := os.Symlink(link, target); err != nil {
return errors.Wrapf(err, "failed to create symlink: %s", target)
return fmt.Errorf("failed to create symlink: %s: %w", target, err)
}
case (fi.Mode() & os.ModeDevice) == os.ModeDevice:
if err := copyDevice(target, fi); err != nil {
return errors.Wrapf(err, "failed to create device")
return fmt.Errorf("failed to create device: %w", err)
}
default:
// TODO: Support pipes and sockets
return errors.Wrapf(err, "unsupported mode %s", fi.Mode())
}
if err := copyFileInfo(fi, target); err != nil {
return errors.Wrap(err, "failed to copy file info")
return fmt.Errorf("unsupported mode %s: %w", fi.Mode(), err)
}
if err := copyXAttrs(target, source, o.xeh); err != nil {
return errors.Wrap(err, "failed to copy xattrs")
if err := copyFileInfo(fi, source, target); err != nil {
return fmt.Errorf("failed to copy file info: %w", err)
}
if err := copyXAttrs(target, source, o.xex, o.xeh); err != nil {
return fmt.Errorf("failed to copy xattrs: %w", err)
}
}
@@ -163,12 +178,12 @@ func copyDirectory(dst, src string, inodes map[uint64]string, o *copyDirOpts) er
func CopyFile(target, source string) error {
src, err := os.Open(source)
if err != nil {
return errors.Wrapf(err, "failed to open source %s", source)
return fmt.Errorf("failed to open source %s: %w", source, err)
}
defer src.Close()
tgt, err := os.Create(target)
if err != nil {
return errors.Wrapf(err, "failed to open target %s", target)
return fmt.Errorf("failed to open target %s: %w", target, err)
}
defer tgt.Close()
+36
View File
@@ -0,0 +1,36 @@
//go:build darwin
// +build darwin
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package fs
import (
"errors"
"os"
"syscall"
"golang.org/x/sys/unix"
)
func copyDevice(dst string, fi os.FileInfo) error {
st, ok := fi.Sys().(*syscall.Stat_t)
if !ok {
return errors.New("unsupported stat type")
}
return unix.Mknod(dst, uint32(fi.Mode()), int(st.Rdev))
}
+36
View File
@@ -0,0 +1,36 @@
//go:build openbsd || solaris || netbsd
// +build openbsd solaris netbsd
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package fs
import (
"errors"
"os"
"syscall"
"golang.org/x/sys/unix"
)
func copyDevice(dst string, fi os.FileInfo) error {
st, ok := fi.Sys().(*syscall.Stat_t)
if !ok {
return errors.New("unsupported stat type")
}
return unix.Mknod(dst, uint32(fi.Mode()), int(st.Rdev))
}
+36
View File
@@ -0,0 +1,36 @@
//go:build freebsd
// +build freebsd
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package fs
import (
"errors"
"os"
"syscall"
"golang.org/x/sys/unix"
)
func copyDevice(dst string, fi os.FileInfo) error {
st, ok := fi.Sys().(*syscall.Stat_t)
if !ok {
return errors.New("unsupported stat type")
}
return unix.Mknod(dst, uint32(fi.Mode()), st.Rdev)
}
+25 -18
View File
@@ -17,16 +17,17 @@
package fs
import (
"errors"
"fmt"
"io"
"os"
"syscall"
"github.com/containerd/continuity/sysx"
"github.com/pkg/errors"
"golang.org/x/sys/unix"
)
func copyFileInfo(fi os.FileInfo, name string) error {
func copyFileInfo(fi os.FileInfo, src, name string) error {
st := fi.Sys().(*syscall.Stat_t)
if err := os.Lchown(name, int(st.Uid), int(st.Gid)); err != nil {
if os.IsPermission(err) {
@@ -41,13 +42,13 @@ func copyFileInfo(fi os.FileInfo, name string) error {
}
}
if err != nil {
return errors.Wrapf(err, "failed to chown %s", name)
return fmt.Errorf("failed to chown %s: %w", name, err)
}
}
if (fi.Mode() & os.ModeSymlink) != os.ModeSymlink {
if err := os.Chmod(name, fi.Mode()); err != nil {
return errors.Wrapf(err, "failed to chmod %s", name)
return fmt.Errorf("failed to chmod %s: %w", name, err)
}
}
@@ -56,7 +57,7 @@ func copyFileInfo(fi os.FileInfo, name string) error {
unix.NsecToTimespec(syscall.TimespecToNsec(StatMtime(st))),
}
if err := unix.UtimesNanoAt(unix.AT_FDCWD, name, timespec, unix.AT_SYMLINK_NOFOLLOW); err != nil {
return errors.Wrapf(err, "failed to utime %s", name)
return fmt.Errorf("failed to utime %s: %w", name, err)
}
return nil
@@ -67,7 +68,7 @@ const maxSSizeT = int64(^uint(0) >> 1)
func copyFileContent(dst, src *os.File) error {
st, err := src.Stat()
if err != nil {
return errors.Wrap(err, "unable to stat source")
return fmt.Errorf("unable to stat source: %w", err)
}
size := st.Size()
@@ -88,13 +89,16 @@ func copyFileContent(dst, src *os.File) error {
n, err := unix.CopyFileRange(srcFd, nil, dstFd, nil, copySize, 0)
if err != nil {
if (err != unix.ENOSYS && err != unix.EXDEV) || !first {
return errors.Wrap(err, "copy file range failed")
return fmt.Errorf("copy file range failed: %w", err)
}
buf := bufferPool.Get().(*[]byte)
_, err = io.CopyBuffer(dst, src, *buf)
bufferPool.Put(buf)
return errors.Wrap(err, "userspace copy failed")
if err != nil {
return fmt.Errorf("userspace copy failed: %w", err)
}
return nil
}
first = false
@@ -104,30 +108,33 @@ func copyFileContent(dst, src *os.File) error {
return nil
}
func copyXAttrs(dst, src string, xeh XAttrErrorHandler) error {
func copyXAttrs(dst, src string, excludes map[string]struct{}, errorHandler XAttrErrorHandler) error {
xattrKeys, err := sysx.LListxattr(src)
if err != nil {
e := errors.Wrapf(err, "failed to list xattrs on %s", src)
if xeh != nil {
e = xeh(dst, src, "", e)
e := fmt.Errorf("failed to list xattrs on %s: %w", src, err)
if errorHandler != nil {
e = errorHandler(dst, src, "", e)
}
return e
}
for _, xattr := range xattrKeys {
if _, exclude := excludes[xattr]; exclude {
continue
}
data, err := sysx.LGetxattr(src, xattr)
if err != nil {
e := errors.Wrapf(err, "failed to get xattr %q on %s", xattr, src)
if xeh != nil {
if e = xeh(dst, src, xattr, e); e == nil {
e := fmt.Errorf("failed to get xattr %q on %s: %w", xattr, src, err)
if errorHandler != nil {
if e = errorHandler(dst, src, xattr, e); e == nil {
continue
}
}
return e
}
if err := sysx.LSetxattr(dst, xattr, data, 0); err != nil {
e := errors.Wrapf(err, "failed to set xattr %q on %s", xattr, dst)
if xeh != nil {
if e = xeh(dst, src, xattr, e); e == nil {
e := fmt.Errorf("failed to set xattr %q on %s: %w", xattr, dst, err)
if errorHandler != nil {
if e = errorHandler(dst, src, xattr, e); e == nil {
continue
}
}
+21 -27
View File
@@ -1,4 +1,5 @@
// +build darwin freebsd openbsd solaris
//go:build darwin || freebsd || openbsd || netbsd || solaris
// +build darwin freebsd openbsd netbsd solaris
/*
Copyright The containerd Authors.
@@ -19,16 +20,15 @@
package fs
import (
"fmt"
"io"
"os"
"syscall"
"github.com/containerd/continuity/sysx"
"github.com/pkg/errors"
"golang.org/x/sys/unix"
)
func copyFileInfo(fi os.FileInfo, name string) error {
func copyFileInfo(fi os.FileInfo, src, name string) error {
st := fi.Sys().(*syscall.Stat_t)
if err := os.Lchown(name, int(st.Uid), int(st.Gid)); err != nil {
if os.IsPermission(err) {
@@ -43,19 +43,18 @@ func copyFileInfo(fi os.FileInfo, name string) error {
}
}
if err != nil {
return errors.Wrapf(err, "failed to chown %s", name)
return fmt.Errorf("failed to chown %s: %w", name, err)
}
}
if (fi.Mode() & os.ModeSymlink) != os.ModeSymlink {
if err := os.Chmod(name, fi.Mode()); err != nil {
return errors.Wrapf(err, "failed to chmod %s", name)
return fmt.Errorf("failed to chmod %s: %w", name, err)
}
}
timespec := []syscall.Timespec{StatAtime(st), StatMtime(st)}
if err := syscall.UtimesNano(name, timespec); err != nil {
return errors.Wrapf(err, "failed to utime %s", name)
if err := utimesNano(name, StatAtime(st), StatMtime(st)); err != nil {
return fmt.Errorf("failed to utime %s: %w", name, err)
}
return nil
@@ -69,30 +68,33 @@ func copyFileContent(dst, src *os.File) error {
return err
}
func copyXAttrs(dst, src string, xeh XAttrErrorHandler) error {
func copyXAttrs(dst, src string, excludes map[string]struct{}, errorHandler XAttrErrorHandler) error {
xattrKeys, err := sysx.LListxattr(src)
if err != nil {
e := errors.Wrapf(err, "failed to list xattrs on %s", src)
if xeh != nil {
e = xeh(dst, src, "", e)
e := fmt.Errorf("failed to list xattrs on %s: %w", src, err)
if errorHandler != nil {
e = errorHandler(dst, src, "", e)
}
return e
}
for _, xattr := range xattrKeys {
if _, exclude := excludes[xattr]; exclude {
continue
}
data, err := sysx.LGetxattr(src, xattr)
if err != nil {
e := errors.Wrapf(err, "failed to get xattr %q on %s", xattr, src)
if xeh != nil {
if e = xeh(dst, src, xattr, e); e == nil {
e := fmt.Errorf("failed to get xattr %q on %s: %w", xattr, src, err)
if errorHandler != nil {
if e = errorHandler(dst, src, xattr, e); e == nil {
continue
}
}
return e
}
if err := sysx.LSetxattr(dst, xattr, data, 0); err != nil {
e := errors.Wrapf(err, "failed to set xattr %q on %s", xattr, dst)
if xeh != nil {
if e = xeh(dst, src, xattr, e); e == nil {
e := fmt.Errorf("failed to set xattr %q on %s: %w", xattr, dst, err)
if errorHandler != nil {
if e = errorHandler(dst, src, xattr, e); e == nil {
continue
}
}
@@ -102,11 +104,3 @@ func copyXAttrs(dst, src string, xeh XAttrErrorHandler) error {
return nil
}
func copyDevice(dst string, fi os.FileInfo) error {
st, ok := fi.Sys().(*syscall.Stat_t)
if !ok {
return errors.New("unsupported stat type")
}
return unix.Mknod(dst, uint32(fi.Mode()), int(st.Rdev))
}
+46 -5
View File
@@ -17,19 +17,60 @@
package fs
import (
"errors"
"fmt"
"io"
"os"
"github.com/pkg/errors"
winio "github.com/Microsoft/go-winio"
"golang.org/x/sys/windows"
)
func copyFileInfo(fi os.FileInfo, name string) error {
const (
seTakeOwnershipPrivilege = "SeTakeOwnershipPrivilege"
)
func copyFileInfo(fi os.FileInfo, src, name string) error {
if err := os.Chmod(name, fi.Mode()); err != nil {
return errors.Wrapf(err, "failed to chmod %s", name)
return fmt.Errorf("failed to chmod %s: %w", name, err)
}
// TODO: copy windows specific metadata
// Copy file ownership and ACL
// We need SeRestorePrivilege and SeTakeOwnershipPrivilege in order
// to restore security info on a file, especially if we're trying to
// apply security info which includes SIDs not necessarily present on
// the host.
privileges := []string{winio.SeRestorePrivilege, seTakeOwnershipPrivilege}
if err := winio.EnableProcessPrivileges(privileges); err != nil {
return err
}
defer winio.DisableProcessPrivileges(privileges)
secInfo, err := windows.GetNamedSecurityInfo(
src, windows.SE_FILE_OBJECT,
windows.OWNER_SECURITY_INFORMATION|windows.DACL_SECURITY_INFORMATION)
if err != nil {
return err
}
dacl, _, err := secInfo.DACL()
if err != nil {
return err
}
sid, _, err := secInfo.Owner()
if err != nil {
return err
}
if err := windows.SetNamedSecurityInfo(
name, windows.SE_FILE_OBJECT,
windows.OWNER_SECURITY_INFORMATION|windows.DACL_SECURITY_INFORMATION,
sid, nil, dacl, nil); err != nil {
return err
}
return nil
}
@@ -40,7 +81,7 @@ func copyFileContent(dst, src *os.File) error {
return err
}
func copyXAttrs(dst, src string, xeh XAttrErrorHandler) error {
func copyXAttrs(dst, src string, excludes map[string]struct{}, errorHandler XAttrErrorHandler) error {
return nil
}
+4 -3
View File
@@ -1,3 +1,4 @@
//go:build !windows
// +build !windows
/*
@@ -20,11 +21,11 @@ package fs
import (
"bytes"
"fmt"
"os"
"syscall"
"github.com/containerd/continuity/sysx"
"github.com/pkg/errors"
)
// detectDirDiff returns diff dir options if a directory could
@@ -56,11 +57,11 @@ func compareSysStat(s1, s2 interface{}) (bool, error) {
func compareCapabilities(p1, p2 string) (bool, error) {
c1, err := sysx.LGetxattr(p1, "security.capability")
if err != nil && err != sysx.ENODATA {
return false, errors.Wrapf(err, "failed to get xattr for %s", p1)
return false, fmt.Errorf("failed to get xattr for %s: %w", p1, err)
}
c2, err := sysx.LGetxattr(p2, "security.capability")
if err != nil && err != sysx.ENODATA {
return false, errors.Wrapf(err, "failed to get xattr for %s", p2)
return false, fmt.Errorf("failed to get xattr for %s: %w", p2, err)
}
return bytes.Equal(c1, c2), nil
}
+1
View File
@@ -1,3 +1,4 @@
//go:build linux
// +build linux
/*
+17 -8
View File
@@ -1,3 +1,4 @@
//go:build !windows
// +build !windows
/*
@@ -25,6 +26,14 @@ import (
"syscall"
)
// blocksUnitSize is the unit used by `st_blocks` in `stat` in bytes.
// See https://man7.org/linux/man-pages/man2/stat.2.html
// st_blocks
// This field indicates the number of blocks allocated to the
// file, in 512-byte units. (This may be smaller than
// st_size/512 when the file has holes.)
const blocksUnitSize = 512
type inode struct {
// TODO(stevvooe): Can probably reduce memory usage by not tracking
// device, but we can leave this right for now.
@@ -33,10 +42,8 @@ type inode struct {
func newInode(stat *syscall.Stat_t) inode {
return inode{
// Dev is uint32 on darwin/bsd, uint64 on linux/solaris
dev: uint64(stat.Dev), // nolint: unconvert
// Ino is uint32 on bsd, uint64 on darwin/linux/solaris
ino: uint64(stat.Ino), // nolint: unconvert
dev: uint64(stat.Dev), //nolint: unconvert // dev is uint32 on darwin/bsd, uint64 on linux/solaris/freebsd
ino: uint64(stat.Ino), //nolint: unconvert // ino is uint32 on bsd, uint64 on darwin/linux/solaris/freebsd
}
}
@@ -59,10 +66,11 @@ func diskUsage(ctx context.Context, roots ...string) (Usage, error) {
default:
}
inoKey := newInode(fi.Sys().(*syscall.Stat_t))
stat := fi.Sys().(*syscall.Stat_t)
inoKey := newInode(stat)
if _, ok := inodes[inoKey]; !ok {
inodes[inoKey] = struct{}{}
size += fi.Size()
size += stat.Blocks * blocksUnitSize
}
return nil
@@ -89,10 +97,11 @@ func diffUsage(ctx context.Context, a, b string) (Usage, error) {
}
if kind == ChangeKindAdd || kind == ChangeKindModify {
inoKey := newInode(fi.Sys().(*syscall.Stat_t))
stat := fi.Sys().(*syscall.Stat_t)
inoKey := newInode(stat)
if _, ok := inodes[inoKey]; !ok {
inodes[inoKey] = struct{}{}
size += fi.Size()
size += stat.Blocks * blocksUnitSize
}
return nil
+1
View File
@@ -1,3 +1,4 @@
//go:build windows
// +build windows
/*
+2 -2
View File
@@ -1,3 +1,4 @@
//go:build !windows
// +build !windows
/*
@@ -29,6 +30,5 @@ func getLinkInfo(fi os.FileInfo) (uint64, bool) {
return 0, false
}
// Ino is uint32 on bsd, uint64 on darwin/linux/solaris
return uint64(s.Ino), !fi.IsDir() && s.Nlink > 1 // nolint: unconvert
return uint64(s.Ino), !fi.IsDir() && s.Nlink > 1 //nolint: unconvert // ino is uint32 on bsd, uint64 on darwin/linux/solaris
}
+1 -2
View File
@@ -19,11 +19,10 @@ package fs
import (
"bytes"
"context"
"errors"
"io"
"os"
"path/filepath"
"github.com/pkg/errors"
)
var (
@@ -1,4 +1,5 @@
// +build linux openbsd
//go:build linux || openbsd || solaris
// +build linux openbsd solaris
/*
Copyright The containerd Authors.
@@ -40,6 +41,5 @@ func StatMtime(st *syscall.Stat_t) syscall.Timespec {
// StatATimeAsTime returns st.Atim as a time.Time
func StatATimeAsTime(st *syscall.Stat_t) time.Time {
// The int64 conversions ensure the line compiles for 32-bit systems as well.
return time.Unix(int64(st.Atim.Sec), int64(st.Atim.Nsec)) // nolint: unconvert
return time.Unix(st.Atim.Unix())
}
@@ -1,4 +1,5 @@
// +build darwin freebsd
//go:build darwin || freebsd || netbsd
// +build darwin freebsd netbsd
/*
Copyright The containerd Authors.
@@ -40,5 +41,5 @@ func StatMtime(st *syscall.Stat_t) syscall.Timespec {
// StatATimeAsTime returns the access time as a time.Time
func StatATimeAsTime(st *syscall.Stat_t) time.Time {
return time.Unix(int64(st.Atimespec.Sec), int64(st.Atimespec.Nsec)) // nolint: unconvert
return time.Unix(st.Atimespec.Unix())
}
+33
View File
@@ -0,0 +1,33 @@
//go:build !(windows || linux)
// +build !windows,!linux
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package fs
import (
"syscall"
"golang.org/x/sys/unix"
)
func utimesNano(name string, atime, mtime syscall.Timespec) error {
at := unix.NsecToTimespec(atime.Nano())
mt := unix.NsecToTimespec(mtime.Nano())
utimes := [2]unix.Timespec{at, mt}
return unix.UtimesNanoAt(unix.AT_FDCWD, name, utimes[0:], unix.AT_SYMLINK_NOFOLLOW)
}