//go:build !windows && !plan9 && !js package unix import ( "fmt" "math" "strconv" "sync" "golang.org/x/sys/unix" "src.elv.sh/pkg/eval/errs" "src.elv.sh/pkg/eval/vals" "src.elv.sh/pkg/eval/vars" ) const ( validUmaskMsg = "integer in the range [0..0o777]" ) //elvdoc:var umask // // The file mode creation mask. Its value is a string in Elvish octal // representation; e.g. 0o027. This makes it possible to use it in any context // that expects a `$number`. // // When assigning a new value a string is implicitly treated as an // octal number. If that fails the usual rules for interpreting // [numbers](./language.html#number) are used. The following are equivalent: // `set unix:umask = 027` and `set unix:umask = 0o27`. You can also assign to it // a `float64` data type that has no fractional component. The assigned value // must be within the range [0 ... 0o777], otherwise the assignment will throw // an exception. // // You can do a temporary assignment to affect a single command; e.g. `umask=077 // touch a_file`. After the command completes the old umask will be restored. // **Warning**: Since the umask applies to the entire process, not individual // threads, changing it temporarily in this manner is dangerous if you are doing // anything in parallel, such as via the [`peach`](builtin.html#peach) command. // UmaskVariable is a variable whose value always reflects the current file // creation permission mask. Setting it changes the current file creation // permission mask for the process (not an individual thread). type UmaskVariable struct{} var _ vars.Var = UmaskVariable{} // Guard against concurrent fetch and assignment of $unix:umask. This assumes // no other part of the elvish code base will call unix.Umask() as it only // protects against races involving the aforementioned Elvish var. var umaskMutex sync.Mutex // Get returns the current file creation umask as a string. func (UmaskVariable) Get() interface{} { // Note: The seemingly redundant syscall is because the unix.Umask() API // doesn't allow querying the current value without changing it. So ensure // we reinstate the current value. umaskMutex.Lock() defer umaskMutex.Unlock() umask := unix.Umask(0) unix.Umask(umask) return fmt.Sprintf("0o%03o", umask) } // Set changes the current file creation umask. It can be called with a string // (the usual case) or a float64. func (UmaskVariable) Set(v interface{}) error { var umask int switch v := v.(type) { case string: i, err := strconv.ParseInt(v, 8, 0) if err != nil { i, err = strconv.ParseInt(v, 0, 0) if err != nil { return errs.BadValue{ What: "umask", Valid: validUmaskMsg, Actual: vals.ToString(v)} } } umask = int(i) case int: // We don't bother supporting big.Int or bit.Rat because no valid umask value would be // represented by those types. umask = v case float64: intPart, fracPart := math.Modf(v) if fracPart != 0 { return errs.BadValue{ What: "umask", Valid: validUmaskMsg, Actual: vals.ToString(v)} } umask = int(intPart) default: return errs.BadValue{ What: "umask", Valid: validUmaskMsg, Actual: vals.Kind(v)} } if umask < 0 || umask > 0o777 { return errs.OutOfRange{ What: "umask", ValidLow: "0", ValidHigh: "0o777", Actual: fmt.Sprintf("%O", umask)} } umaskMutex.Lock() defer umaskMutex.Unlock() unix.Umask(umask) return nil }