// Copyright (C) 2015 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at https://mozilla.org/MPL/2.0/.

// +build !windows

package osutil

import (
	"runtime"
	"syscall"
)

const (
	darwinOpenMax = 10240
)

// MaximizeOpenFileLimit tries to set the resource limit RLIMIT_NOFILE (number
// of open file descriptors) to the max (hard limit), if the current (soft
// limit) is below the max. Returns the new (though possibly unchanged) limit,
// or an error if it could not be changed.
func MaximizeOpenFileLimit() (int, error) {
	// Get the current limit on number of open files.
	var lim syscall.Rlimit
	if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &lim); err != nil {
		return 0, err
	}

	// If we're already at max, there's no need to try to raise the limit.
	if lim.Cur >= lim.Max {
		return int(lim.Cur), nil
	}

	// macOS doesn't like a soft limit greater then OPEN_MAX
	// See also: man setrlimit
	if runtime.GOOS == "darwin" && lim.Max > darwinOpenMax {
		lim.Cur = darwinOpenMax
	}

	// Try to increase the limit to the max.
	oldLimit := lim.Cur
	lim.Cur = lim.Max
	if err := syscall.Setrlimit(syscall.RLIMIT_NOFILE, &lim); err != nil {
		return int(oldLimit), err
	}

	// If the set succeeded, perform a new get to see what happened. We might
	// have gotten a value lower than the one in lim.Max, if lim.Max was
	// something that indicated "unlimited" (i.e. intmax).
	if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &lim); err != nil {
		// We don't really know the correct value here since Getrlimit
		// mysteriously failed after working once... Shouldn't ever happen, I
		// think.
		return 0, err
	}

	return int(lim.Cur), nil
}
