rlimit.go 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254
  1. //go:build !windows && !plan9 && !js
  2. package unix
  3. import (
  4. "fmt"
  5. "sync"
  6. "golang.org/x/sys/unix"
  7. "src.elv.sh/pkg/eval/errs"
  8. "src.elv.sh/pkg/eval/vals"
  9. )
  10. //elvdoc:var rlimits
  11. //
  12. // A map describing resource limits of the current process.
  13. //
  14. // Each key is a string corresponds to a resource, and each value is a map with
  15. // keys `&cur` and `&max`, describing the soft and hard limits of that resource.
  16. // A missing `&cur` key means that there is no soft limit; a missing `&max` key
  17. // means that there is no hard limit.
  18. //
  19. // The following resources are supported, some only present on certain OSes:
  20. //
  21. // | Key | Resource | Unit | OS |
  22. // | ------------ | ------------------ | ------- | ------------------ |
  23. // | `core` | Core file | bytes | all |
  24. // | `cpu` | CPU time | seconds | all |
  25. // | `data` | Data segment | bytes | all |
  26. // | `fsize` | File size | bytes | all |
  27. // | `memlock` | Locked memory | bytes | all |
  28. // | `nofile` | File descriptors | number | all |
  29. // | `nproc` | Processes | number | all |
  30. // | `rss` | Resident set size | bytes | all |
  31. // | `stack` | Stack segment | bytes | all |
  32. // | `as` | Address space | bytes | Linux, Free/NetBSD |
  33. // | `nthr` | Threads | number | NetBSD |
  34. // | `sbsize` | Socket buffers | bytes | NetBSD |
  35. // | `locks` | File locks | number | Linux |
  36. // | `msgqueue` | Message queues | bytes | Linux |
  37. // | `nice` | 20 - nice value | | Linux |
  38. // | `rtprio` | Real-time priority | | Linux |
  39. // | `rttime` | Real-time CPU time | seconds | Linux |
  40. // | `sigpending` | Signals queued | number | Linux |
  41. //
  42. // For the exact semantics of each resource, see the man page of `getrlimit`:
  43. // [Linux](https://man7.org/linux/man-pages/man2/setrlimit.2.html),
  44. // [macOS](https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/getrlimit.2.html),
  45. // [FreeBSD](https://www.freebsd.org/cgi/man.cgi?query=getrlimit),
  46. // [NetBSD](https://man.netbsd.org/getrlimit.2),
  47. // [OpenBSD](https://man.openbsd.org/getrlimit.2). A key `foo` in the Elvish API
  48. // corresponds to `RLIMIT_FOO` in the C API.
  49. //
  50. // Examples:
  51. //
  52. // ```elvish-transcript
  53. // ~> put $unix:rlimits
  54. // ▶ [&nofile=[&cur=(num 256)] &fsize=[&] &nproc=[&max=(num 2666) &cur=(num 2666)] &memlock=[&] &cpu=[&] &core=[&cur=(num 0)] &stack=[&max=(num 67092480) &cur=(num 8372224)] &rss=[&] &data=[&]]
  55. // ~> # mimic Bash's "ulimit -a"
  56. // ~> keys $unix:rlimits | order | each {|key|
  57. // var m = $unix:rlimits[$key]
  58. // fn get {|k| if (has-key $m $k) { put $m[$k] } else { put unlimited } }
  59. // printf "%-7v %-9v %-9v\n" $key (get cur) (get max)
  60. // }
  61. // core 0 unlimited
  62. // cpu unlimited unlimited
  63. // data unlimited unlimited
  64. // fsize unlimited unlimited
  65. // memlock unlimited unlimited
  66. // nofile 256 unlimited
  67. // nproc 2666 2666
  68. // rss unlimited unlimited
  69. // stack 8372224 67092480
  70. // ~> # Decrease the soft limit on file descriptors
  71. // ~> set unix:rlimits[nofile][cur] = 100
  72. // ~> put $unix:rlimits[nofile]
  73. // ▶ [&cur=(num 100)]
  74. // ~> # Remove the soft limit on file descriptors
  75. // ~> del unix:rlimits[nofile][cur]
  76. // ~> put $unix:rlimits[nofile]
  77. // ▶ [&]
  78. // ```
  79. type rlimitsVar struct{}
  80. var (
  81. getRlimit = unix.Getrlimit
  82. setRlimit = unix.Setrlimit
  83. )
  84. var (
  85. rlimitMutex sync.Mutex
  86. rlimits map[int]*unix.Rlimit
  87. )
  88. func (rlimitsVar) Get() interface{} {
  89. rlimitMutex.Lock()
  90. defer rlimitMutex.Unlock()
  91. initRlimits()
  92. rlimitsMap := vals.EmptyMap
  93. for res, lim := range rlimits {
  94. limMap := vals.EmptyMap
  95. if lim.Cur != unix.RLIM_INFINITY {
  96. limMap = limMap.Assoc("cur", convertRlimT(lim.Cur))
  97. }
  98. if lim.Max != unix.RLIM_INFINITY {
  99. limMap = limMap.Assoc("max", convertRlimT(lim.Max))
  100. }
  101. rlimitsMap = rlimitsMap.Assoc(rlimitKeys[res], limMap)
  102. }
  103. return rlimitsMap
  104. }
  105. func (rlimitsVar) Set(v interface{}) error {
  106. newRlimits, err := parseRlimitsMap(v)
  107. if err != nil {
  108. return err
  109. }
  110. rlimitMutex.Lock()
  111. defer rlimitMutex.Unlock()
  112. initRlimits()
  113. for res := range rlimits {
  114. if *rlimits[res] != *newRlimits[res] {
  115. err := setRlimit(res, newRlimits[res])
  116. if err != nil {
  117. return fmt.Errorf("setrlimit %s: %w", rlimitKeys[res], err)
  118. }
  119. rlimits[res] = newRlimits[res]
  120. }
  121. }
  122. return nil
  123. }
  124. func initRlimits() {
  125. if rlimits != nil {
  126. return
  127. }
  128. rlimits = make(map[int]*unix.Rlimit)
  129. for res := range rlimitKeys {
  130. var lim unix.Rlimit
  131. err := getRlimit(res, &lim)
  132. if err == nil {
  133. rlimits[res] = &lim
  134. } else {
  135. // Since getrlimit should only ever return an error when the
  136. // resource is not supported, this should normally never happen. But
  137. // be defensive nonetheless.
  138. logger.Println("initialize rlimits", res, rlimitKeys[res], err)
  139. // Remove this key, so that rlimitKeys is always consistent with the
  140. // value of rlimits (and thus $unix:rlimits).
  141. delete(rlimitKeys, res)
  142. }
  143. }
  144. }
  145. func parseRlimitsMap(val interface{}) (map[int]*unix.Rlimit, error) {
  146. if err := checkRlimitsMapKeys(val); err != nil {
  147. return nil, err
  148. }
  149. limits := make(map[int]*unix.Rlimit, len(rlimitKeys))
  150. for res, key := range rlimitKeys {
  151. limitVal, err := vals.Index(val, key)
  152. if err != nil {
  153. return nil, err
  154. }
  155. limits[res], err = parseRlimitMap(limitVal)
  156. if err != nil {
  157. return nil, err
  158. }
  159. }
  160. return limits, nil
  161. }
  162. func checkRlimitsMapKeys(val interface{}) error {
  163. wantedKeys := make(map[string]struct{}, len(rlimitKeys))
  164. for _, key := range rlimitKeys {
  165. wantedKeys[key] = struct{}{}
  166. }
  167. var errKey error
  168. err := vals.IterateKeys(val, func(k interface{}) bool {
  169. ks, ok := k.(string)
  170. if !ok {
  171. errKey = errs.BadValue{What: "key of $unix:rlimits",
  172. Valid: "string", Actual: vals.Kind(k)}
  173. return false
  174. }
  175. if _, valid := wantedKeys[ks]; !valid {
  176. errKey = errs.BadValue{What: "key of $unix:rlimits",
  177. Valid: "valid resource key", Actual: vals.ReprPlain(k)}
  178. return false
  179. }
  180. delete(wantedKeys, ks)
  181. return true
  182. })
  183. if err != nil {
  184. return errs.BadValue{What: "$unix:rlimits",
  185. Valid: "map", Actual: vals.Kind(val)}
  186. }
  187. if errKey != nil {
  188. return errKey
  189. }
  190. if len(wantedKeys) > 0 {
  191. return errs.BadValue{What: "$unix:rlimits",
  192. Valid: "map containing all resource keys", Actual: vals.ReprPlain(val)}
  193. }
  194. return nil
  195. }
  196. func parseRlimitMap(val interface{}) (*unix.Rlimit, error) {
  197. if err := checkRlimitMapKeys(val); err != nil {
  198. return nil, err
  199. }
  200. cur, err := indexRlimitMap(val, "cur")
  201. if err != nil {
  202. return nil, err
  203. }
  204. max, err := indexRlimitMap(val, "max")
  205. if err != nil {
  206. return nil, err
  207. }
  208. return &unix.Rlimit{Cur: cur, Max: max}, nil
  209. }
  210. func checkRlimitMapKeys(val interface{}) error {
  211. var errKey error
  212. err := vals.IterateKeys(val, func(k interface{}) bool {
  213. if k != "cur" && k != "max" {
  214. errKey = errs.BadValue{What: "key of rlimit value",
  215. Valid: "cur or max", Actual: vals.ReprPlain(k)}
  216. return false
  217. }
  218. return true
  219. })
  220. if err != nil {
  221. return errs.BadValue{What: "rlimit value",
  222. Valid: "map", Actual: vals.Kind(val)}
  223. }
  224. return errKey
  225. }
  226. func indexRlimitMap(m interface{}, key string) (rlimT, error) {
  227. val, err := vals.Index(m, key)
  228. if err != nil {
  229. return unix.RLIM_INFINITY, nil
  230. }
  231. if r, ok := parseRlimT(val); ok {
  232. return r, nil
  233. }
  234. return 0, errs.BadValue{What: key + " in rlimit value",
  235. Valid: rlimTValid, Actual: vals.ReprPlain(val)}
  236. }