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  }