github.com/markusbkk/elvish@v0.0.0-20231204143114-91dc52438621/pkg/mods/unix/umask.go (about)

     1  //go:build !windows && !plan9 && !js
     2  // +build !windows,!plan9,!js
     3  
     4  package unix
     5  
     6  import (
     7  	"fmt"
     8  	"math"
     9  	"strconv"
    10  	"sync"
    11  
    12  	"golang.org/x/sys/unix"
    13  
    14  	"github.com/markusbkk/elvish/pkg/eval/errs"
    15  	"github.com/markusbkk/elvish/pkg/eval/vals"
    16  	"github.com/markusbkk/elvish/pkg/eval/vars"
    17  )
    18  
    19  const (
    20  	validUmaskMsg = "integer in the range [0..0o777]"
    21  )
    22  
    23  //elvdoc:var umask
    24  //
    25  // The file mode creation mask. Its value is a string in Elvish octal
    26  // representation; e.g. 0o027. This makes it possible to use it in any context
    27  // that expects a `$number`.
    28  //
    29  // When assigning a new value a string is implicitly treated as an
    30  // octal number. If that fails the usual rules for interpreting
    31  // [numbers](./language.html#number) are used. The following are equivalent:
    32  // `set unix:umask = 027` and `set unix:umask = 0o27`. You can also assign to it
    33  // a `float64` data type that has no fractional component. The assigned value
    34  // must be within the range [0 ... 0o777], otherwise the assignment will throw
    35  // an exception.
    36  //
    37  // You can do a temporary assignment to affect a single command; e.g. `umask=077
    38  // touch a_file`. After the command completes the old umask will be restored.
    39  // **Warning**: Since the umask applies to the entire process, not individual
    40  // threads, changing it temporarily in this manner is dangerous if you are doing
    41  // anything in parallel, such as via the [`peach`](builtin.html#peach) command.
    42  
    43  // UmaskVariable is a variable whose value always reflects the current file
    44  // creation permission mask. Setting it changes the current file creation
    45  // permission mask for the process (not an individual thread).
    46  type UmaskVariable struct{}
    47  
    48  var _ vars.Var = UmaskVariable{}
    49  
    50  // Guard against concurrent fetch and assignment of $unix:umask. This assumes
    51  // no other part of the elvish code base will call unix.Umask() as it only
    52  // protects against races involving the aforementioned Elvish var.
    53  var umaskMutex sync.Mutex
    54  
    55  // Get returns the current file creation umask as a string.
    56  func (UmaskVariable) Get() interface{} {
    57  	// Note: The seemingly redundant syscall is because the unix.Umask() API
    58  	// doesn't allow querying the current value without changing it. So ensure
    59  	// we reinstate the current value.
    60  	umaskMutex.Lock()
    61  	defer umaskMutex.Unlock()
    62  	umask := unix.Umask(0)
    63  	unix.Umask(umask)
    64  	return fmt.Sprintf("0o%03o", umask)
    65  }
    66  
    67  // Set changes the current file creation umask. It can be called with a string
    68  // (the usual case) or a float64.
    69  func (UmaskVariable) Set(v interface{}) error {
    70  	var umask int
    71  
    72  	switch v := v.(type) {
    73  	case string:
    74  		i, err := strconv.ParseInt(v, 8, 0)
    75  		if err != nil {
    76  			i, err = strconv.ParseInt(v, 0, 0)
    77  			if err != nil {
    78  				return errs.BadValue{
    79  					What: "umask", Valid: validUmaskMsg, Actual: vals.ToString(v)}
    80  			}
    81  		}
    82  		umask = int(i)
    83  	case int:
    84  		// We don't bother supporting big.Int or bit.Rat because no valid umask value would be
    85  		// represented by those types.
    86  		umask = v
    87  	case float64:
    88  		intPart, fracPart := math.Modf(v)
    89  		if fracPart != 0 {
    90  			return errs.BadValue{
    91  				What: "umask", Valid: validUmaskMsg, Actual: vals.ToString(v)}
    92  		}
    93  		umask = int(intPart)
    94  	default:
    95  		return errs.BadValue{
    96  			What: "umask", Valid: validUmaskMsg, Actual: vals.Kind(v)}
    97  	}
    98  
    99  	if umask < 0 || umask > 0o777 {
   100  		return errs.OutOfRange{
   101  			What: "umask", ValidLow: "0", ValidHigh: "0o777",
   102  			Actual: fmt.Sprintf("%O", umask)}
   103  	}
   104  
   105  	umaskMutex.Lock()
   106  	defer umaskMutex.Unlock()
   107  	unix.Umask(umask)
   108  	return nil
   109  }