src.elv.sh@v0.21.0-dev.0.20240515223629-06979efb9a2a/pkg/mods/unix/umask.go (about)

     1  //go:build unix
     2  
     3  package unix
     4  
     5  import (
     6  	"fmt"
     7  	"math"
     8  	"math/big"
     9  	"strconv"
    10  	"sync"
    11  
    12  	"golang.org/x/sys/unix"
    13  
    14  	"src.elv.sh/pkg/eval/errs"
    15  	"src.elv.sh/pkg/eval/vals"
    16  	"src.elv.sh/pkg/eval/vars"
    17  )
    18  
    19  const (
    20  	validUmaskMsg = "integer in the range [0..0o777]"
    21  )
    22  
    23  // UmaskVariable is a variable whose value always reflects the current file
    24  // creation permission mask. Setting it changes the current file creation
    25  // permission mask for the process (not an individual thread).
    26  type UmaskVariable struct{}
    27  
    28  var _ vars.Var = UmaskVariable{}
    29  
    30  // There is no way to query the current umask without changing it, so we store
    31  // the umask value in a variable, and initialize it during startup. It needs to
    32  // be mutex-guarded since it could be read or written concurrently.
    33  //
    34  // This assumes no other part of the Elvish code base involved in the
    35  // interpreter ever calls unix.Umask, which is guaranteed by the
    36  // check-content.sh script.
    37  var (
    38  	umaskVal   int
    39  	umaskMutex sync.RWMutex
    40  )
    41  
    42  func init() {
    43  	// Init functions are run concurrently, so it's normally impossible to
    44  	// observe the temporary value.
    45  	//
    46  	// Even if there is some pathological init logic (e.g. goroutine from init
    47  	// functions), the failure pattern is relative safe because we are setting
    48  	// the temporary umask to the most restrictive value possible.
    49  	umask := unix.Umask(0o777)
    50  	unix.Umask(umask)
    51  	umaskVal = umask
    52  }
    53  
    54  // Get returns the current file creation umask as a string.
    55  func (UmaskVariable) Get() any {
    56  	umaskMutex.RLock()
    57  	defer umaskMutex.RUnlock()
    58  	return fmt.Sprintf("0o%03o", umaskVal)
    59  }
    60  
    61  // Set changes the current file creation umask. It can be called with a string
    62  // or a number. Strings are treated as octal numbers by default, unless they
    63  // have an explicit base prefix like 0x or 0b.
    64  func (UmaskVariable) Set(v any) error {
    65  	umask, err := parseUmask(v)
    66  	if err != nil {
    67  		return err
    68  	}
    69  
    70  	umaskMutex.Lock()
    71  	defer umaskMutex.Unlock()
    72  	unix.Umask(umask)
    73  	umaskVal = umask
    74  	return nil
    75  }
    76  
    77  func parseUmask(v any) (int, error) {
    78  	var umask int
    79  
    80  	switch v := v.(type) {
    81  	case string:
    82  		i, err := strconv.ParseInt(v, 8, 0)
    83  		if err != nil {
    84  			i, err = strconv.ParseInt(v, 0, 0)
    85  			if err != nil {
    86  				return -1, errs.BadValue{
    87  					What: "umask", Valid: validUmaskMsg, Actual: vals.ToString(v)}
    88  			}
    89  		}
    90  		umask = int(i)
    91  	case int:
    92  		umask = v
    93  	case float64:
    94  		intPart, fracPart := math.Modf(v)
    95  		if fracPart != 0 {
    96  			return -1, errs.BadValue{
    97  				What: "umask", Valid: validUmaskMsg, Actual: vals.ToString(v)}
    98  		}
    99  		umask = int(intPart)
   100  	case *big.Int:
   101  		return -1, errs.OutOfRange{
   102  			What: "umask", ValidLow: "0", ValidHigh: "0o777",
   103  			Actual: vals.ToString(v)}
   104  	case *big.Rat:
   105  		return -1, errs.BadValue{
   106  			What: "umask", Valid: validUmaskMsg, Actual: vals.ToString(v)}
   107  	default:
   108  		return -1, errs.BadValue{
   109  			What: "umask", Valid: validUmaskMsg, Actual: vals.Kind(v)}
   110  	}
   111  
   112  	if umask < 0 || umask > 0o777 {
   113  		return -1, errs.OutOfRange{
   114  			What: "umask", ValidLow: "0", ValidHigh: "0o777",
   115  			Actual: fmt.Sprintf("%O", umask)}
   116  	}
   117  	return umask, nil
   118  }