github.com/tetratelabs/wazero@v1.2.1/internal/sysfs/futimens.go (about)

     1  package sysfs
     2  
     3  import (
     4  	"syscall"
     5  	"time"
     6  	"unsafe"
     7  
     8  	"github.com/tetratelabs/wazero/internal/fsapi"
     9  	"github.com/tetratelabs/wazero/internal/platform"
    10  )
    11  
    12  const (
    13  	// UTIME_NOW is a special syscall.Timespec NSec value used to set the
    14  	// file's timestamp to a value close to, but not greater than the current
    15  	// system time.
    16  	UTIME_NOW = _UTIME_NOW
    17  
    18  	// UTIME_OMIT is a special syscall.Timespec NSec value used to avoid
    19  	// setting the file's timestamp.
    20  	UTIME_OMIT = _UTIME_OMIT
    21  )
    22  
    23  // Utimens set file access and modification times on a path resolved to the
    24  // current working directory, at nanosecond precision.
    25  //
    26  // # Parameters
    27  //
    28  // The `times` parameter includes the access and modification timestamps to
    29  // assign. Special syscall.Timespec NSec values UTIME_NOW and UTIME_OMIT may be
    30  // specified instead of real timestamps. A nil `times` parameter behaves the
    31  // same as if both were set to UTIME_NOW.
    32  //
    33  // When the `symlinkFollow` parameter is true and the path is a symbolic link,
    34  // the target of expanding that link is updated.
    35  //
    36  // # Errors
    37  //
    38  // A zero syscall.Errno is success. The below are expected otherwise:
    39  //   - syscall.ENOSYS: the implementation does not support this function.
    40  //   - syscall.EINVAL: `path` is invalid.
    41  //   - syscall.EEXIST: `path` exists and is a directory.
    42  //   - syscall.ENOTDIR: `path` exists and is a file.
    43  //
    44  // # Notes
    45  //
    46  //   - This is like syscall.UtimesNano and `utimensat` with `AT_FDCWD` in
    47  //     POSIX. See https://pubs.opengroup.org/onlinepubs/9699919799/functions/futimens.html
    48  func Utimens(path string, times *[2]syscall.Timespec, symlinkFollow bool) syscall.Errno {
    49  	err := utimens(path, times, symlinkFollow)
    50  	return platform.UnwrapOSError(err)
    51  }
    52  
    53  var _zero uintptr //nolint:unused
    54  
    55  func timesToPtr(times *[2]syscall.Timespec) unsafe.Pointer { //nolint:unused
    56  	var _p0 unsafe.Pointer
    57  	if times != nil {
    58  		_p0 = unsafe.Pointer(&times[0])
    59  	} else {
    60  		_p0 = unsafe.Pointer(&_zero)
    61  	}
    62  	return _p0
    63  }
    64  
    65  func utimensPortable(path string, times *[2]syscall.Timespec, symlinkFollow bool) error { //nolint:unused
    66  	if !symlinkFollow {
    67  		return syscall.ENOSYS
    68  	}
    69  
    70  	// Handle when both inputs are current system time.
    71  	if times == nil || times[0].Nsec == UTIME_NOW && times[1].Nsec == UTIME_NOW {
    72  		ts := nowTimespec()
    73  		return syscall.UtimesNano(path, []syscall.Timespec{ts, ts})
    74  	}
    75  
    76  	// When both inputs are omitted, there is nothing to change.
    77  	if times[0].Nsec == UTIME_OMIT && times[1].Nsec == UTIME_OMIT {
    78  		return nil
    79  	}
    80  
    81  	// Handle when neither input are special values
    82  	if times[0].Nsec != UTIME_NOW && times[1].Nsec != UTIME_NOW &&
    83  		times[0].Nsec != UTIME_OMIT && times[1].Nsec != UTIME_OMIT {
    84  		return syscall.UtimesNano(path, times[:])
    85  	}
    86  
    87  	// Now, either atim or mtim is a special value, but not both.
    88  
    89  	// Now, either one of the inputs is a special value, or neither. This means
    90  	// we don't have a risk of re-reading the clock or re-doing stat.
    91  	if atim, err := normalizeTimespec(path, times, 0); err != 0 {
    92  		return err
    93  	} else if mtim, err := normalizeTimespec(path, times, 1); err != 0 {
    94  		return err
    95  	} else {
    96  		return syscall.UtimesNano(path, []syscall.Timespec{atim, mtim})
    97  	}
    98  }
    99  
   100  func normalizeTimespec(path string, times *[2]syscall.Timespec, i int) (ts syscall.Timespec, err syscall.Errno) { //nolint:unused
   101  	switch times[i].Nsec {
   102  	case UTIME_NOW: // declined in Go per golang/go#31880.
   103  		ts = nowTimespec()
   104  		return
   105  	case UTIME_OMIT:
   106  		// UTIME_OMIT is expensive until progress is made in Go, as it requires a
   107  		// stat to read-back the value to re-apply.
   108  		// - https://github.com/golang/go/issues/32558.
   109  		// - https://go-review.googlesource.com/c/go/+/219638 (unmerged)
   110  		var st fsapi.Stat_t
   111  		if st, err = stat(path); err != 0 {
   112  			return
   113  		}
   114  		switch i {
   115  		case 0:
   116  			ts = syscall.NsecToTimespec(st.Atim)
   117  		case 1:
   118  			ts = syscall.NsecToTimespec(st.Mtim)
   119  		default:
   120  			panic("BUG")
   121  		}
   122  		return
   123  	default: // not special
   124  		ts = times[i]
   125  		return
   126  	}
   127  }
   128  
   129  func nowTimespec() syscall.Timespec { //nolint:unused
   130  	now := time.Now().UnixNano()
   131  	return syscall.NsecToTimespec(now)
   132  }