umask.go 2.9 KB

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