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 }