// Copyright 2018 Tobias Klauser. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package sysconf import ( "bufio" "io/ioutil" "os" "runtime" "strconv" "strings" "github.com/tklauser/numcpus" "golang.org/x/sys/unix" ) const ( // CLK_TCK is a constant on Linux, see e.g. // https://git.musl-libc.org/cgit/musl/tree/src/conf/sysconf.c#n30 and // https://github.com/containerd/cgroups/pull/12 _SYSTEM_CLK_TCK = 100 ) func readProcFsInt64(path string, fallback int64) int64 { data, err := ioutil.ReadFile(path) if err != nil { return fallback } i, err := strconv.ParseInt(string(data[:len(data)-1]), 0, 64) if err != nil { return fallback } return i } // getMemPages computes mem*unit/os.Getpagesize(), but avoids overflowing int64. func getMemPages(mem uint64, unit uint32) int64 { pageSize := os.Getpagesize() for unit > 1 && pageSize > 1 { unit >>= 1 pageSize >>= 1 } mem *= uint64(unit) for pageSize > 1 { pageSize >>= 1 mem >>= 1 } return int64(mem) } func getPhysPages() int64 { var si unix.Sysinfo_t err := unix.Sysinfo(&si) if err != nil { return int64(0) } return getMemPages(uint64(si.Totalram), si.Unit) } func getAvPhysPages() int64 { var si unix.Sysinfo_t err := unix.Sysinfo(&si) if err != nil { return int64(0) } return getMemPages(uint64(si.Freeram), si.Unit) } func getNprocsSysfs() (int64, error) { n, err := numcpus.GetOnline() return int64(n), err } func getNprocsProcStat() (int64, error) { f, err := os.Open("/proc/stat") if err != nil { return -1, err } defer f.Close() count := int64(0) s := bufio.NewScanner(f) for s.Scan() { if line := strings.TrimSpace(s.Text()); strings.HasPrefix(line, "cpu") { l := strings.SplitN(line, " ", 2) _, err := strconv.ParseInt(l[0][3:], 10, 64) if err == nil { count++ } } else { // The current format of /proc/stat has all the // cpu* lines at the beginning. Assume this // stays this way. break } } return count, nil } func getNprocs() int64 { count, err := getNprocsSysfs() if err == nil { return count } count, err = getNprocsProcStat() if err == nil { return count } // default to the value determined at runtime startup if all else fails return int64(runtime.NumCPU()) } func getNprocsConf() int64 { // TODO(tk): read /sys/devices/system/cpu/present instead? d, err := os.Open("/sys/devices/system/cpu") if err == nil { defer d.Close() fis, err := d.Readdir(-1) if err == nil { count := int64(0) for _, fi := range fis { if name := fi.Name(); fi.IsDir() && strings.HasPrefix(name, "cpu") { _, err := strconv.ParseInt(name[3:], 10, 64) if err == nil { count++ } } } return count } } // TODO(tk): fall back to reading /proc/cpuinfo on legacy systems // without sysfs? return getNprocs() } func hasClock(clockid int32) bool { var res unix.Timespec if err := unix.ClockGetres(clockid, &res); err != nil { return false } return true } func max(a, b int64) int64 { if a > b { return a } return b } func sysconf(name int) (int64, error) { switch name { case SC_AIO_LISTIO_MAX: return -1, nil case SC_AIO_MAX: return -1, nil case SC_AIO_PRIO_DELTA_MAX: return _AIO_PRIO_DELTA_MAX, nil case SC_ARG_MAX: argMax := int64(_POSIX_ARG_MAX) var rlim unix.Rlimit if err := unix.Getrlimit(unix.RLIMIT_STACK, &rlim); err == nil { argMax = max(argMax, int64(rlim.Cur/4)) } return argMax, nil case SC_ATEXIT_MAX: return _INT_MAX, nil case SC_CHILD_MAX: childMax := int64(-1) var rlim unix.Rlimit if err := unix.Getrlimit(unix.RLIMIT_NPROC, &rlim); err == nil && rlim.Cur != unix.RLIM_INFINITY { childMax = int64(rlim.Cur) } return childMax, nil case SC_CLK_TCK: return _SYSTEM_CLK_TCK, nil case SC_DELAYTIMER_MAX: return _DELAYTIMER_MAX, nil case SC_GETGR_R_SIZE_MAX: return _NSS_BUFLEN_GROUP, nil case SC_GETPW_R_SIZE_MAX: return _NSS_BUFLEN_PASSWD, nil case SC_MQ_OPEN_MAX: return -1, nil case SC_MQ_PRIO_MAX: return _MQ_PRIO_MAX, nil case SC_NGROUPS_MAX: return readProcFsInt64("/proc/sys/kernel/ngroups_max", _NGROUPS_MAX), nil case SC_OPEN_MAX: openMax := int64(_OPEN_MAX) var rlim unix.Rlimit if err := unix.Getrlimit(unix.RLIMIT_NOFILE, &rlim); err == nil { openMax = int64(rlim.Cur) } return openMax, nil case SC_RTSIG_MAX: return _RTSIG_MAX, nil case SC_SEM_NSEMS_MAX: return -1, nil case SC_SEM_VALUE_MAX: return _SEM_VALUE_MAX, nil case SC_SIGQUEUE_MAX: var rlim unix.Rlimit if err := unix.Getrlimit(unix.RLIMIT_SIGPENDING, &rlim); err == nil { return int64(rlim.Cur), nil } return readProcFsInt64("/proc/sys/kernel/rtsig-max", _POSIX_SIGQUEUE_MAX), nil case SC_STREAM_MAX: return _STREAM_MAX, nil case SC_THREAD_DESTRUCTOR_ITERATIONS: return _POSIX_THREAD_DESTRUCTOR_ITERATIONS, nil case SC_THREAD_KEYS_MAX: return _PTHREAD_KEYS_MAX, nil case SC_THREAD_PRIO_INHERIT: return _POSIX_THREAD_PRIO_INHERIT, nil case SC_THREAD_PRIO_PROTECT: return _POSIX_THREAD_PRIO_PROTECT, nil case SC_THREAD_STACK_MIN: return _PTHREAD_STACK_MIN, nil case SC_THREAD_THREADS_MAX: return -1, nil case SC_TIMER_MAX: return -1, nil case SC_TTY_NAME_MAX: return _TTY_NAME_MAX, nil case SC_TZNAME_MAX: return -1, nil case SC_CPUTIME: if hasClock(unix.CLOCK_PROCESS_CPUTIME_ID) { return _POSIX_VERSION, nil } return -1, nil case SC_MONOTONIC_CLOCK: if hasClock(unix.CLOCK_MONOTONIC) { return _POSIX_VERSION, nil } return -1, nil case SC_SAVED_IDS: return _POSIX_SAVED_IDS, nil case SC_SPAWN: return _POSIX_SPAWN, nil case SC_SPIN_LOCKS: return _POSIX_SPIN_LOCKS, nil case SC_SPORADIC_SERVER: return _POSIX_SPORADIC_SERVER, nil case SC_SYNCHRONIZED_IO: return _POSIX_SYNCHRONIZED_IO, nil case SC_THREAD_ATTR_STACKADDR: return _POSIX_THREAD_ATTR_STACKADDR, nil case SC_THREAD_ATTR_STACKSIZE: return _POSIX_THREAD_ATTR_STACKSIZE, nil case SC_THREAD_CPUTIME: if hasClock(unix.CLOCK_THREAD_CPUTIME_ID) { return _POSIX_VERSION, nil } return -1, nil case SC_THREAD_PRIORITY_SCHEDULING: return _POSIX_THREAD_PRIORITY_SCHEDULING, nil case SC_THREAD_PROCESS_SHARED: return _POSIX_THREAD_PROCESS_SHARED, nil case SC_THREAD_SAFE_FUNCTIONS: return _POSIX_THREAD_SAFE_FUNCTIONS, nil case SC_THREAD_SPORADIC_SERVER: return _POSIX_THREAD_SPORADIC_SERVER, nil case SC_TRACE: return _POSIX_TRACE, nil case SC_TRACE_EVENT_FILTER: return _POSIX_TRACE_EVENT_FILTER, nil case SC_TRACE_EVENT_NAME_MAX: return -1, nil case SC_TRACE_INHERIT: return _POSIX_TRACE_INHERIT, nil case SC_TRACE_LOG: return _POSIX_TRACE_LOG, nil case SC_TRACE_NAME_MAX: return -1, nil case SC_TRACE_SYS_MAX: return -1, nil case SC_TRACE_USER_EVENT_MAX: return -1, nil case SC_TYPED_MEMORY_OBJECTS: return _POSIX_TYPED_MEMORY_OBJECTS, nil case SC_V7_ILP32_OFF32: return _POSIX_V7_ILP32_OFF32, nil case SC_V7_ILP32_OFFBIG: return _POSIX_V7_ILP32_OFFBIG, nil case SC_V7_LP64_OFF64: return _POSIX_V7_LP64_OFF64, nil case SC_V7_LPBIG_OFFBIG: return _POSIX_V7_LPBIG_OFFBIG, nil case SC_V6_ILP32_OFF32: return _POSIX_V6_ILP32_OFF32, nil case SC_V6_ILP32_OFFBIG: return _POSIX_V6_ILP32_OFFBIG, nil case SC_V6_LP64_OFF64: return _POSIX_V6_LP64_OFF64, nil case SC_V6_LPBIG_OFFBIG: return _POSIX_V6_LPBIG_OFFBIG, nil case SC_2_C_VERSION: return _POSIX2_C_VERSION, nil case SC_2_CHAR_TERM: return _POSIX2_CHAR_TERM, nil case SC_2_PBS, SC_2_PBS_ACCOUNTING, SC_2_PBS_CHECKPOINT, SC_2_PBS_LOCATE, SC_2_PBS_MESSAGE, SC_2_PBS_TRACK: return -1, nil case SC_2_UPE: return -1, nil case SC_XOPEN_CRYPT: // removed in glibc 2.28 return -1, nil case SC_XOPEN_ENH_I18N: return _XOPEN_ENH_I18N, nil case SC_XOPEN_REALTIME: return _XOPEN_REALTIME, nil case SC_XOPEN_REALTIME_THREADS: return _XOPEN_REALTIME_THREADS, nil case SC_XOPEN_SHM: return _XOPEN_SHM, nil case SC_XOPEN_STREAMS: return -1, nil case SC_XOPEN_UNIX: return _XOPEN_UNIX, nil case SC_XOPEN_VERSION: return _XOPEN_VERSION, nil case SC_XOPEN_XCU_VERSION: return _XOPEN_XCU_VERSION, nil case SC_PHYS_PAGES: return getPhysPages(), nil case SC_AVPHYS_PAGES: return getAvPhysPages(), nil case SC_NPROCESSORS_CONF: return getNprocsConf(), nil case SC_NPROCESSORS_ONLN: return getNprocs(), nil case SC_UIO_MAXIOV: // same as _SC_IOV_MAX return _UIO_MAXIOV, nil } return sysconfGeneric(name) }