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 }