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