[FreeBSD] process resource limits (#1859) (#809)

diff --git a/HISTORY.rst b/HISTORY.rst
index 1ef5573..d92dce7 100644
--- a/HISTORY.rst
+++ b/HISTORY.rst
@@ -7,7 +7,8 @@
 
 **Enhancements**
 
-- 893_: implement `Process.environ()` on BSD family. (patch by Armin Gruner)
+- 809_: [FreeBSD] add support for `Process.rlimit()`.
+- 893_: [BSD] add support for `Process.environ()` (patch by Armin Gruner)
 - 1830_: [UNIX] `net_if_stats()`'s `isup` also checks whether the NIC is
   running (meaning Wi-Fi or ethernet cable is connected).  (patch by Chris Burger)
 - 1837_: [Linux] improved battery detection and charge "secsleft" calculation
diff --git a/docs/_static/css/custom.css b/docs/_static/css/custom.css
index c5c201e..e88f530 100644
--- a/docs/_static/css/custom.css
+++ b/docs/_static/css/custom.css
@@ -4,7 +4,7 @@
 }
 
 .rst-content dl:not(.docutils) {
-    margin: 0px 0px 0px 0px;
+    margin: 0px 0px 0px 0px !important;
 }
 
 .data dd {
diff --git a/docs/index.rst b/docs/index.rst
index 3898f9d..c4ff6a2 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -1348,16 +1348,17 @@
 
       >>> import psutil
       >>> p = psutil.Process()
-      >>> # process may open no more than 128 file descriptors
-      >>> p.rlimit(psutil.RLIMIT_NOFILE, (128, 128))
-      >>> # process may create files no bigger than 1024 bytes
-      >>> p.rlimit(psutil.RLIMIT_FSIZE, (1024, 1024))
-      >>> # get
-      >>> p.rlimit(psutil.RLIMIT_FSIZE)
+      >>> p.rlimit(psutil.RLIMIT_NOFILE, (128, 128))   # process can open max 128 file descriptors
+      >>> p.rlimit(psutil.RLIMIT_FSIZE, (1024, 1024))  # can create files no bigger than 1024 bytes
+      >>> p.rlimit(psutil.RLIMIT_FSIZE)                # get
       (1024, 1024)
       >>>
 
-    Availability: Linux
+    Also see `procinfo.py`_ script.
+
+    Availability: Linux, FreeBSD
+
+    .. versionchanged:: 5.7.3 added FreeBSD support
 
   .. method:: io_counters()
 
@@ -2247,29 +2248,43 @@
 Process resources constants
 ---------------------------
 
-.. data:: RLIM_INFINITY
-.. data:: RLIMIT_AS
-.. data:: RLIMIT_CORE
-.. data:: RLIMIT_CPU
-.. data:: RLIMIT_DATA
-.. data:: RLIMIT_FSIZE
-.. data:: RLIMIT_LOCKS
-.. data:: RLIMIT_MEMLOCK
-.. data:: RLIMIT_MSGQUEUE
-.. data:: RLIMIT_NICE
-.. data:: RLIMIT_NOFILE
-.. data:: RLIMIT_NPROC
-.. data:: RLIMIT_RSS
-.. data:: RLIMIT_RTPRIO
-.. data:: RLIMIT_RTTIME
-.. data:: RLIMIT_SIGPENDING
-.. data:: RLIMIT_STACK
+Linux / FreeBSD:
 
-  Constants used for getting and setting process resource limits to be used in
-  conjunction with :meth:`psutil.Process.rlimit()`. See `man prlimit`_ for
-  further information.
+  .. data:: RLIM_INFINITY
+  .. data:: RLIMIT_AS
+  .. data:: RLIMIT_CORE
+  .. data:: RLIMIT_CPU
+  .. data:: RLIMIT_DATA
+  .. data:: RLIMIT_FSIZE
+  .. data:: RLIMIT_MEMLOCK
+  .. data:: RLIMIT_NOFILE
+  .. data:: RLIMIT_NPROC
+  .. data:: RLIMIT_RSS
+  .. data:: RLIMIT_STACK
 
-  Availability: Linux
+Linux specific:
+
+  .. data:: RLIMIT_LOCKS
+  .. data:: RLIMIT_MSGQUEUE
+  .. data:: RLIMIT_NICE
+  .. data:: RLIMIT_RTPRIO
+  .. data:: RLIMIT_RTTIME
+  .. data:: RLIMIT_SIGPENDING
+
+FreeBSD specific:
+
+  .. data:: RLIMIT_SWAP
+  .. data:: RLIMIT_SBSIZE
+  .. data:: RLIMIT_NPTS
+
+Constants used for getting and setting process resource limits to be used in
+conjunction with :meth:`psutil.Process.rlimit()`. See `resource.getrlimit`_
+for further information.
+
+Availability: Linux, FreeBSD
+
+.. versionchanged:: 5.7.3 added FreeBSD support, added ``RLIMIT_SWAP``,
+  ``RLIMIT_SBSIZE``, ``RLIMIT_NPTS``.
 
 Connections constants
 ---------------------
@@ -2513,11 +2528,6 @@
 To report a security vulnerability, please use the `Tidelift security
 contact`_.  Tidelift will coordinate the fix and disclosure.
 
-.. _`Giampaolo Rodola`: https://gmpy.dev/about
-.. _`donation`: https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A9ZS7PKKRM3S8
-.. _Tidelift security contact: https://tidelift.com/security
-.. _Tidelift Subscription: https://tidelift.com/subscription/pkg/pypi-psutil?utm_source=pypi-psutil&utm_medium=referral&utm_campaign=readme
-
 Development guide
 =================
 
@@ -2874,13 +2884,12 @@
 .. _`cpu_distribution.py`: https://github.com/giampaolo/psutil/blob/master/scripts/cpu_distribution.py
 .. _`development guide`: https://github.com/giampaolo/psutil/blob/master/docs/DEVGUIDE.rst
 .. _`disk_usage.py`: https://github.com/giampaolo/psutil/blob/master/scripts/disk_usage.py
-.. _`donation`: https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A9ZS7PKKRM3S8
 .. _`enum`: https://docs.python.org/3/library/enum.html#module-enum
 .. _`fans.py`: https://github.com/giampaolo/psutil/blob/master/scripts/fans.py
 .. _`GetDriveType`: https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/nf-fileapi-getdrivetypea
+.. _`GetExitCodeProcess`: https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-getexitcodeprocess
 .. _`getfsstat`: http://www.manpagez.com/man/2/getfsstat/
 .. _`GetPriorityClass`: https://docs.microsoft.com/en-us/windows/desktop/api/processthreadsapi/nf-processthreadsapi-getpriorityclass
-.. _`GetExitCodeProcess`: https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-getexitcodeprocess
 .. _`Giampaolo Rodola`: https://gmpy.dev/about
 .. _`hash`: https://docs.python.org/3/library/functions.html#hash
 .. _`ifconfig.py`: https://github.com/giampaolo/psutil/blob/master/scripts/ifconfig.py
@@ -2908,6 +2917,7 @@
 .. _`os.times`: https://docs.python.org//library/os.html#os.times
 .. _`pmap.py`: https://github.com/giampaolo/psutil/blob/master/scripts/pmap.py
 .. _`PROCESS_MEMORY_COUNTERS_EX`: https://docs.microsoft.com/en-us/windows/desktop/api/psapi/ns-psapi-_process_memory_counters_ex
+.. _`procinfo.py`: https://github.com/giampaolo/psutil/blob/master/scripts/procinfo.py
 .. _`procsmem.py`: https://github.com/giampaolo/psutil/blob/master/scripts/procsmem.py
 .. _`resource.getrlimit`: https://docs.python.org/3/library/resource.html#resource.getrlimit
 .. _`resource.setrlimit`: https://docs.python.org/3/library/resource.html#resource.setrlimit
@@ -2920,9 +2930,10 @@
 .. _`SOCK_SEQPACKET`: https://docs.python.org/3/library/socket.html#socket.SOCK_SEQPACKET
 .. _`SOCK_STREAM`: https://docs.python.org/3/library/socket.html#socket.SOCK_STREAM
 .. _`socket.fromfd`: https://docs.python.org/3/library/socket.html#socket.fromfd
-.. _`subprocess.Popen`: https://docs.python.org/3/library/subprocess.html#subprocess.Popen
 .. _`subprocess.Popen.wait`: https://docs.python.org/3/library/subprocess.html#subprocess.Popen.wait
+.. _`subprocess.Popen`: https://docs.python.org/3/library/subprocess.html#subprocess.Popen
 .. _`temperatures.py`: https://github.com/giampaolo/psutil/blob/master/scripts/temperatures.py
 .. _`TerminateProcess`: https://docs.microsoft.com/en-us/windows/desktop/api/processthreadsapi/nf-processthreadsapi-terminateprocess
 .. _Tidelift security contact: https://tidelift.com/security
 .. _Tidelift Subscription: https://tidelift.com/subscription/pkg/pypi-psutil?utm_source=pypi-psutil&utm_medium=referral&utm_campaign=readme
+.. _Tidelift Subscription: https://tidelift.com/subscription/pkg/pypi-psutil?utm_source=pypi-psutil&utm_medium=referral&utm_campaign=readme
diff --git a/psutil/__init__.py b/psutil/__init__.py
index 2108a40..cc4e79c 100644
--- a/psutil/__init__.py
+++ b/psutil/__init__.py
@@ -102,44 +102,6 @@
     from ._pslinux import IOPRIO_CLASS_IDLE  # NOQA
     from ._pslinux import IOPRIO_CLASS_NONE  # NOQA
     from ._pslinux import IOPRIO_CLASS_RT  # NOQA
-    # Linux >= 2.6.36
-    if _psplatform.HAS_PRLIMIT:
-        from ._psutil_linux import RLIM_INFINITY  # NOQA
-        from ._psutil_linux import RLIMIT_AS  # NOQA
-        from ._psutil_linux import RLIMIT_CORE  # NOQA
-        from ._psutil_linux import RLIMIT_CPU  # NOQA
-        from ._psutil_linux import RLIMIT_DATA  # NOQA
-        from ._psutil_linux import RLIMIT_FSIZE  # NOQA
-        from ._psutil_linux import RLIMIT_LOCKS  # NOQA
-        from ._psutil_linux import RLIMIT_MEMLOCK  # NOQA
-        from ._psutil_linux import RLIMIT_NOFILE  # NOQA
-        from ._psutil_linux import RLIMIT_NPROC  # NOQA
-        from ._psutil_linux import RLIMIT_RSS  # NOQA
-        from ._psutil_linux import RLIMIT_STACK  # NOQA
-        # Kinda ugly but considerably faster than using hasattr() and
-        # setattr() against the module object (we are at import time:
-        # speed matters).
-        from . import _psutil_linux
-        try:
-            RLIMIT_MSGQUEUE = _psutil_linux.RLIMIT_MSGQUEUE
-        except AttributeError:
-            pass
-        try:
-            RLIMIT_NICE = _psutil_linux.RLIMIT_NICE
-        except AttributeError:
-            pass
-        try:
-            RLIMIT_RTPRIO = _psutil_linux.RLIMIT_RTPRIO
-        except AttributeError:
-            pass
-        try:
-            RLIMIT_RTTIME = _psutil_linux.RLIMIT_RTTIME
-        except AttributeError:
-            pass
-        try:
-            RLIMIT_SIGPENDING = _psutil_linux.RLIMIT_SIGPENDING
-        except AttributeError:
-            pass
 
 elif WINDOWS:
     from . import _pswindows as _psplatform
@@ -197,6 +159,7 @@
     "CONN_ESTABLISHED", "CONN_SYN_SENT", "CONN_SYN_RECV", "CONN_FIN_WAIT1",
     "CONN_FIN_WAIT2", "CONN_TIME_WAIT", "CONN_CLOSE", "CONN_CLOSE_WAIT",
     "CONN_LAST_ACK", "CONN_LISTEN", "CONN_CLOSING", "CONN_NONE",
+    # "CONN_IDLE", "CONN_BOUND",
 
     "AF_LINK",
 
@@ -207,6 +170,11 @@
     "BSD", "FREEBSD", "LINUX", "NETBSD", "OPENBSD", "MACOS", "OSX", "POSIX",
     "SUNOS", "WINDOWS", "AIX",
 
+    # "RLIM_INFINITY", "RLIMIT_AS", "RLIMIT_CORE", "RLIMIT_CPU", "RLIMIT_DATA",
+    # "RLIMIT_FSIZE", "RLIMIT_LOCKS", "RLIMIT_MEMLOCK", "RLIMIT_NOFILE",
+    # "RLIMIT_NPROC", "RLIMIT_RSS", "RLIMIT_STACK", "RLIMIT_MSGQUEUE",
+    # "RLIMIT_NICE", "RLIMIT_RTPRIO", "RLIMIT_RTTIME", "RLIMIT_SIGPENDING",
+
     # classes
     "Process", "Popen",
 
@@ -222,9 +190,22 @@
     "users", "boot_time",                                           # others
 ]
 
+__all__.extend(_psplatform.__extra__all__)
+
+if LINUX or FREEBSD:
+    # Populate global namespace with RLIM* constants.
+    from . import _psutil_posix
+
+    _globals = globals()
+    _name = None
+    for _name in dir(_psutil_posix):
+        if _name.startswith('RLIM') and _name.isupper():
+            _globals[_name] = getattr(_psutil_posix, _name)
+            __all__.append(_name)
+    del _globals, _name
+
 AF_LINK = _psplatform.AF_LINK
 
-__all__.extend(_psplatform.__extra__all__)
 __author__ = "Giampaolo Rodola'"
 __version__ = "5.7.3"
 version_info = tuple([int(num) for num in __version__.split('.')])
@@ -801,7 +782,7 @@
             else:
                 return self._proc.ionice_set(ioclass, value)
 
-    # Linux only
+    # Linux / FreeBSD only
     if hasattr(_psplatform.Process, "rlimit"):
 
         def rlimit(self, resource, limits=None):
@@ -809,15 +790,12 @@
             tuple.
 
             *resource* is one of the RLIMIT_* constants.
-            *limits* is supposed to be a (soft, hard)  tuple.
+            *limits* is supposed to be a (soft, hard) tuple.
 
             See "man prlimit" for further info.
-            Available on Linux only.
+            Available on Linux and FreeBSD only.
             """
-            if limits is None:
-                return self._proc.rlimit(resource)
-            else:
-                return self._proc.rlimit(resource, limits)
+            return self._proc.rlimit(resource, limits)
 
     # Windows, Linux and FreeBSD only
     if hasattr(_psplatform.Process, "cpu_affinity_get"):
@@ -831,7 +809,7 @@
             (Windows, Linux and BSD only).
             """
             if cpus is None:
-                return list(set(self._proc.cpu_affinity_get()))
+                return sorted(set(self._proc.cpu_affinity_get()))
             else:
                 if not cpus:
                     if hasattr(self._proc, "_get_eligible_cpus"):
diff --git a/psutil/_psbsd.py b/psutil/_psbsd.py
index 9565406..428c8bd 100644
--- a/psutil/_psbsd.py
+++ b/psutil/_psbsd.py
@@ -904,3 +904,15 @@
         @wrap_exceptions
         def memory_maps(self):
             return cext.proc_memory_maps(self.pid)
+
+        @wrap_exceptions
+        def rlimit(self, resource, limits=None):
+            if limits is None:
+                return cext.proc_getrlimit(self.pid, resource)
+            else:
+                if len(limits) != 2:
+                    raise ValueError(
+                        "second argument must be a (soft, hard) tuple, "
+                        "got %s" % repr(limits))
+                soft, hard = limits
+                return cext.proc_setrlimit(self.pid, resource, soft, hard)
diff --git a/psutil/_pslinux.py b/psutil/_pslinux.py
index d5ce358..683cef5 100644
--- a/psutil/_pslinux.py
+++ b/psutil/_pslinux.py
@@ -80,12 +80,6 @@
 HAS_CPU_AFFINITY = hasattr(cext, "proc_cpu_affinity_get")
 _DEFAULT = object()
 
-# RLIMIT_* constants, not guaranteed to be present on all kernels
-if HAS_PRLIMIT:
-    for name in dir(cext):
-        if name.startswith('RLIM'):
-            __extra__all__.append(name)
-
 # Number of clock ticks per second
 CLOCK_TICKS = os.sysconf("SC_CLK_TCK")
 PAGESIZE = os.sysconf("SC_PAGE_SIZE")
diff --git a/psutil/_psutil_bsd.c b/psutil/_psutil_bsd.c
index c4450d7..6933260 100644
--- a/psutil/_psutil_bsd.c
+++ b/psutil/_psutil_bsd.c
@@ -1097,6 +1097,10 @@
      "Return process CPU affinity."},
     {"proc_cpu_affinity_set", psutil_proc_cpu_affinity_set, METH_VARARGS,
      "Set process CPU affinity."},
+    {"proc_getrlimit", psutil_proc_getrlimit, METH_VARARGS,
+     "Get process resource limits."},
+    {"proc_setrlimit", psutil_proc_setrlimit, METH_VARARGS,
+     "Set process resource limits."},
     {"cpu_count_phys", psutil_cpu_count_phys, METH_VARARGS,
      "Return an XML string to determine the number physical CPUs."},
 #endif
diff --git a/psutil/_psutil_linux.c b/psutil/_psutil_linux.c
index 4154743..4def969 100644
--- a/psutil/_psutil_linux.c
+++ b/psutil/_psutil_linux.c
@@ -606,7 +606,6 @@
     void init_psutil_linux(void)
 #endif  /* PY_MAJOR_VERSION */
 {
-    PyObject *v;
 #if PY_MAJOR_VERSION >= 3
     PyObject *mod = PyModule_Create(&moduledef);
 #else
@@ -616,48 +615,6 @@
         INITERR;
 
     if (PyModule_AddIntConstant(mod, "version", PSUTIL_VERSION)) INITERR;
-#if PSUTIL_HAVE_PRLIMIT
-    if (PyModule_AddIntConstant(mod, "RLIMIT_AS", RLIMIT_AS)) INITERR;
-    if (PyModule_AddIntConstant(mod, "RLIMIT_CORE", RLIMIT_CORE)) INITERR;
-    if (PyModule_AddIntConstant(mod, "RLIMIT_CPU", RLIMIT_CPU)) INITERR;
-    if (PyModule_AddIntConstant(mod, "RLIMIT_DATA", RLIMIT_DATA)) INITERR;
-    if (PyModule_AddIntConstant(mod, "RLIMIT_FSIZE", RLIMIT_FSIZE)) INITERR;
-    if (PyModule_AddIntConstant(mod, "RLIMIT_LOCKS", RLIMIT_LOCKS)) INITERR;
-    if (PyModule_AddIntConstant(mod, "RLIMIT_MEMLOCK", RLIMIT_MEMLOCK)) INITERR;
-    if (PyModule_AddIntConstant(mod, "RLIMIT_NOFILE", RLIMIT_NOFILE)) INITERR;
-    if (PyModule_AddIntConstant(mod, "RLIMIT_NPROC", RLIMIT_NPROC)) INITERR;
-    if (PyModule_AddIntConstant(mod, "RLIMIT_RSS", RLIMIT_RSS)) INITERR;
-    if (PyModule_AddIntConstant(mod, "RLIMIT_STACK", RLIMIT_STACK)) INITERR;
-
-#if defined(HAVE_LONG_LONG)
-    if (sizeof(RLIM_INFINITY) > sizeof(long)) {
-        v = PyLong_FromLongLong((PY_LONG_LONG) RLIM_INFINITY);
-    } else
-#endif
-    {
-        v = PyLong_FromLong((long) RLIM_INFINITY);
-    }
-    if (v) {
-        PyModule_AddObject(mod, "RLIM_INFINITY", v);
-    }
-
-#ifdef RLIMIT_MSGQUEUE
-    if (PyModule_AddIntConstant(mod, "RLIMIT_MSGQUEUE", RLIMIT_MSGQUEUE)) INITERR;
-#endif
-#ifdef RLIMIT_NICE
-    if (PyModule_AddIntConstant(mod, "RLIMIT_NICE", RLIMIT_NICE)) INITERR;
-#endif
-#ifdef RLIMIT_RTPRIO
-    if (PyModule_AddIntConstant(mod, "RLIMIT_RTPRIO", RLIMIT_RTPRIO)) INITERR;
-#endif
-#ifdef RLIMIT_RTTIME
-    if (PyModule_AddIntConstant(mod, "RLIMIT_RTTIME", RLIMIT_RTTIME)) INITERR;
-#endif
-#ifdef RLIMIT_SIGPENDING
-    if (PyModule_AddIntConstant(mod, "RLIMIT_SIGPENDING", RLIMIT_SIGPENDING))
-        INITERR;
-#endif
-#endif
     if (PyModule_AddIntConstant(mod, "DUPLEX_HALF", DUPLEX_HALF)) INITERR;
     if (PyModule_AddIntConstant(mod, "DUPLEX_FULL", DUPLEX_FULL)) INITERR;
     if (PyModule_AddIntConstant(mod, "DUPLEX_UNKNOWN", DUPLEX_UNKNOWN)) INITERR;
diff --git a/psutil/_psutil_posix.c b/psutil/_psutil_posix.c
index 1182765..876b412 100644
--- a/psutil/_psutil_posix.c
+++ b/psutil/_psutil_posix.c
@@ -41,6 +41,9 @@
 #elif defined(PSUTIL_AIX)
     #include <netdb.h>
 #endif
+#if defined(PSUTIL_LINUX) || defined(PSUTIL_FREEBSD)
+    #include <sys/resource.h>
+#endif
 
 #include "_psutil_common.h"
 
@@ -668,6 +671,102 @@
     if (PyModule_AddIntConstant(mod, "AF_LINK", AF_LINK)) INITERR;
 #endif
 
+#if defined(PSUTIL_LINUX) || defined(PSUTIL_FREEBSD)
+    PyObject *v;
+
+#ifdef RLIMIT_AS
+    if (PyModule_AddIntConstant(mod, "RLIMIT_AS", RLIMIT_AS)) INITERR;
+#endif
+
+#ifdef RLIMIT_CORE
+    if (PyModule_AddIntConstant(mod, "RLIMIT_CORE", RLIMIT_CORE)) INITERR;
+#endif
+
+#ifdef RLIMIT_CPU
+    if (PyModule_AddIntConstant(mod, "RLIMIT_CPU", RLIMIT_CPU)) INITERR;
+#endif
+
+#ifdef RLIMIT_DATA
+    if (PyModule_AddIntConstant(mod, "RLIMIT_DATA", RLIMIT_DATA)) INITERR;
+#endif
+
+#ifdef RLIMIT_FSIZE
+    if (PyModule_AddIntConstant(mod, "RLIMIT_FSIZE", RLIMIT_FSIZE)) INITERR;
+#endif
+
+#ifdef RLIMIT_MEMLOCK
+    if (PyModule_AddIntConstant(mod, "RLIMIT_MEMLOCK", RLIMIT_MEMLOCK)) INITERR;
+#endif
+
+#ifdef RLIMIT_NOFILE
+    if (PyModule_AddIntConstant(mod, "RLIMIT_NOFILE", RLIMIT_NOFILE)) INITERR;
+#endif
+
+#ifdef RLIMIT_NPROC
+    if (PyModule_AddIntConstant(mod, "RLIMIT_NPROC", RLIMIT_NPROC)) INITERR;
+#endif
+
+#ifdef RLIMIT_RSS
+    if (PyModule_AddIntConstant(mod, "RLIMIT_RSS", RLIMIT_RSS)) INITERR;
+#endif
+
+#ifdef RLIMIT_STACK
+    if (PyModule_AddIntConstant(mod, "RLIMIT_STACK", RLIMIT_STACK)) INITERR;
+#endif
+
+// Linux specific
+
+#ifdef RLIMIT_LOCKS
+    if (PyModule_AddIntConstant(mod, "RLIMIT_LOCKS", RLIMIT_LOCKS)) INITERR;
+#endif
+
+#ifdef RLIMIT_MSGQUEUE
+    if (PyModule_AddIntConstant(mod, "RLIMIT_MSGQUEUE", RLIMIT_MSGQUEUE)) INITERR;
+#endif
+
+#ifdef RLIMIT_NICE
+    if (PyModule_AddIntConstant(mod, "RLIMIT_NICE", RLIMIT_NICE)) INITERR;
+#endif
+
+#ifdef RLIMIT_RTPRIO
+    if (PyModule_AddIntConstant(mod, "RLIMIT_RTPRIO", RLIMIT_RTPRIO)) INITERR;
+#endif
+
+#ifdef RLIMIT_RTTIME
+    if (PyModule_AddIntConstant(mod, "RLIMIT_RTTIME", RLIMIT_RTTIME)) INITERR;
+#endif
+
+#ifdef RLIMIT_SIGPENDING
+    if (PyModule_AddIntConstant(mod, "RLIMIT_SIGPENDING", RLIMIT_SIGPENDING)) INITERR;
+#endif
+
+// Free specific
+
+#ifdef RLIMIT_SWAP
+    if (PyModule_AddIntConstant(mod, "RLIMIT_SWAP", RLIMIT_SWAP)) INITERR;
+#endif
+
+#ifdef RLIMIT_SBSIZE
+    if (PyModule_AddIntConstant(mod, "RLIMIT_SBSIZE", RLIMIT_SBSIZE)) INITERR;
+#endif
+
+#ifdef RLIMIT_NPTS
+    if (PyModule_AddIntConstant(mod, "RLIMIT_NPTS", RLIMIT_NPTS)) INITERR;
+#endif
+
+#if defined(HAVE_LONG_LONG)
+    if (sizeof(RLIM_INFINITY) > sizeof(long)) {
+        v = PyLong_FromLongLong((PY_LONG_LONG) RLIM_INFINITY);
+    } else
+#endif
+    {
+        v = PyLong_FromLong((long) RLIM_INFINITY);
+    }
+    if (v) {
+        PyModule_AddObject(mod, "RLIM_INFINITY", v);
+    }
+#endif  // defined(PSUTIL_LINUX) || defined(PSUTIL_FREEBSD)
+
     if (mod == NULL)
         INITERR;
 #if PY_MAJOR_VERSION >= 3
diff --git a/psutil/arch/freebsd/specific.c b/psutil/arch/freebsd/specific.c
index c783264..fcfce13 100644
--- a/psutil/arch/freebsd/specific.c
+++ b/psutil/arch/freebsd/specific.c
@@ -1077,3 +1077,88 @@
         PyErr_SetFromErrno(PyExc_OSError);
     return NULL;
 }
+
+
+/*
+ * An emulation of Linux prlimit(). Returns a (soft, hard) tuple.
+ */
+PyObject *
+psutil_proc_getrlimit(PyObject *self, PyObject *args) {
+    pid_t pid;
+    int ret;
+    int resource;
+    size_t len;
+    int name[5];
+    struct rlimit rlp;
+
+    if (! PyArg_ParseTuple(args, _Py_PARSE_PID "i", &pid, &resource))
+        return NULL;
+
+    name[0] = CTL_KERN;
+    name[1] = KERN_PROC;
+    name[2] = KERN_PROC_RLIMIT;
+    name[3] = pid;
+    name[4] = resource;
+    len = sizeof(rlp);
+
+    ret = sysctl(name, 5, &rlp, &len, NULL, 0);
+    if (ret == -1)
+        return PyErr_SetFromErrno(PyExc_OSError);
+
+#if defined(HAVE_LONG_LONG)
+    return Py_BuildValue("LL",
+                         (PY_LONG_LONG) rlp.rlim_cur,
+                         (PY_LONG_LONG) rlp.rlim_max);
+#else
+    return Py_BuildValue("ll",
+                         (long) rlp.rlim_cur,
+                         (long) rlp.rlim_max);
+#endif
+}
+
+
+/*
+ * An emulation of Linux prlimit() (set).
+ */
+PyObject *
+psutil_proc_setrlimit(PyObject *self, PyObject *args) {
+    pid_t pid;
+    int ret;
+    int resource;
+    int name[5];
+    struct rlimit new;
+    struct rlimit *newp = NULL;
+    PyObject *py_soft = NULL;
+    PyObject *py_hard = NULL;
+
+    if (! PyArg_ParseTuple(
+            args, _Py_PARSE_PID "iOO", &pid, &resource, &py_soft, &py_hard))
+        return NULL;
+
+    name[0] = CTL_KERN;
+    name[1] = KERN_PROC;
+    name[2] = KERN_PROC_RLIMIT;
+    name[3] = pid;
+    name[4] = resource;
+
+#if defined(HAVE_LONG_LONG)
+        new.rlim_cur = PyLong_AsLongLong(py_soft);
+        if (new.rlim_cur == (rlim_t) - 1 && PyErr_Occurred())
+            return NULL;
+        new.rlim_max = PyLong_AsLongLong(py_hard);
+        if (new.rlim_max == (rlim_t) - 1 && PyErr_Occurred())
+            return NULL;
+#else
+        new.rlim_cur = PyLong_AsLong(py_soft);
+        if (new.rlim_cur == (rlim_t) - 1 && PyErr_Occurred())
+            return NULL;
+        new.rlim_max = PyLong_AsLong(py_hard);
+        if (new.rlim_max == (rlim_t) - 1 && PyErr_Occurred())
+            return NULL;
+#endif
+    newp = &new;
+    ret = sysctl(name, 5, NULL, 0, newp, sizeof(*newp));
+    if (ret == -1)
+        return PyErr_SetFromErrno(PyExc_OSError);
+    Py_RETURN_NONE;
+}
diff --git a/psutil/arch/freebsd/specific.h b/psutil/arch/freebsd/specific.h
index 875c816..61c3f07 100644
--- a/psutil/arch/freebsd/specific.h
+++ b/psutil/arch/freebsd/specific.h
@@ -24,6 +24,8 @@
 PyObject* psutil_proc_num_fds(PyObject* self, PyObject* args);
 PyObject* psutil_proc_num_threads(PyObject* self, PyObject* args);
 PyObject* psutil_proc_threads(PyObject* self, PyObject* args);
+PyObject* psutil_proc_getrlimit(PyObject* self, PyObject* args);
+PyObject* psutil_proc_setrlimit(PyObject* self, PyObject* args);
 PyObject* psutil_swap_mem(PyObject* self, PyObject* args);
 PyObject* psutil_virtual_mem(PyObject* self, PyObject* args);
 PyObject* psutil_cpu_stats(PyObject* self, PyObject* args);
diff --git a/psutil/tests/test_bsd.py b/psutil/tests/test_bsd.py
index c91ee3a..06a6508 100755
--- a/psutil/tests/test_bsd.py
+++ b/psutil/tests/test_bsd.py
@@ -474,6 +474,7 @@
                 psutil.sensors_temperatures()["coretemp"][cpu].high,
                 sysctl_result)
 
+
 # =====================================================================
 # --- OpenBSD
 # =====================================================================
diff --git a/psutil/tests/test_contracts.py b/psutil/tests/test_contracts.py
index 2d9e591..39a5256 100755
--- a/psutil/tests/test_contracts.py
+++ b/psutil/tests/test_contracts.py
@@ -89,25 +89,29 @@
     @unittest.skipIf(GITHUB_WHEELS, "not exposed via GITHUB_WHEELS")
     def test_linux_rlimit(self):
         ae = self.assertEqual
-        ae(hasattr(psutil, "RLIM_INFINITY"), LINUX)
-        ae(hasattr(psutil, "RLIMIT_AS"), LINUX)
-        ae(hasattr(psutil, "RLIMIT_CORE"), LINUX)
-        ae(hasattr(psutil, "RLIMIT_CPU"), LINUX)
-        ae(hasattr(psutil, "RLIMIT_DATA"), LINUX)
-        ae(hasattr(psutil, "RLIMIT_FSIZE"), LINUX)
-        ae(hasattr(psutil, "RLIMIT_LOCKS"), LINUX)
-        ae(hasattr(psutil, "RLIMIT_MEMLOCK"), LINUX)
-        ae(hasattr(psutil, "RLIMIT_NOFILE"), LINUX)
-        ae(hasattr(psutil, "RLIMIT_NPROC"), LINUX)
-        ae(hasattr(psutil, "RLIMIT_RSS"), LINUX)
-        ae(hasattr(psutil, "RLIMIT_STACK"), LINUX)
+        ae(hasattr(psutil, "RLIM_INFINITY"), LINUX or FREEBSD)
+        ae(hasattr(psutil, "RLIMIT_AS"), LINUX or FREEBSD)
+        ae(hasattr(psutil, "RLIMIT_CORE"), LINUX or FREEBSD)
+        ae(hasattr(psutil, "RLIMIT_CPU"), LINUX or FREEBSD)
+        ae(hasattr(psutil, "RLIMIT_DATA"), LINUX or FREEBSD)
+        ae(hasattr(psutil, "RLIMIT_FSIZE"), LINUX or FREEBSD)
+        ae(hasattr(psutil, "RLIMIT_MEMLOCK"), LINUX or FREEBSD)
+        ae(hasattr(psutil, "RLIMIT_NOFILE"), LINUX or FREEBSD)
+        ae(hasattr(psutil, "RLIMIT_NPROC"), LINUX or FREEBSD)
+        ae(hasattr(psutil, "RLIMIT_RSS"), LINUX or FREEBSD)
+        ae(hasattr(psutil, "RLIMIT_STACK"), LINUX or FREEBSD)
 
+        ae(hasattr(psutil, "RLIMIT_LOCKS"), LINUX)
         ae(hasattr(psutil, "RLIMIT_MSGQUEUE"), LINUX)  # requires Linux 2.6.8
         ae(hasattr(psutil, "RLIMIT_NICE"), LINUX)  # requires Linux 2.6.12
         ae(hasattr(psutil, "RLIMIT_RTPRIO"), LINUX)  # requires Linux 2.6.12
         ae(hasattr(psutil, "RLIMIT_RTTIME"), LINUX)  # requires Linux 2.6.25
         ae(hasattr(psutil, "RLIMIT_SIGPENDING"), LINUX)  # requires Linux 2.6.8
 
+        ae(hasattr(psutil, "RLIMIT_SWAP"), FREEBSD)
+        ae(hasattr(psutil, "RLIMIT_SBSIZE"), FREEBSD)
+        ae(hasattr(psutil, "RLIMIT_NPTS"), FREEBSD)
+
 
 class TestAvailSystemAPIs(PsutilTestCase):
 
@@ -155,7 +159,7 @@
     @unittest.skipIf(GITHUB_WHEELS, "not exposed via GITHUB_WHEELS")
     def test_rlimit(self):
         # requires Linux 2.6.36
-        self.assertEqual(hasattr(psutil.Process, "rlimit"), LINUX)
+        self.assertEqual(hasattr(psutil.Process, "rlimit"), LINUX or FREEBSD)
 
     def test_io_counters(self):
         hasit = hasattr(psutil.Process, "io_counters")
diff --git a/psutil/tests/test_process.py b/psutil/tests/test_process.py
index b2328ba..0d63979 100755
--- a/psutil/tests/test_process.py
+++ b/psutil/tests/test_process.py
@@ -448,8 +448,9 @@
         self.assertEqual(p.rlimit(psutil.RLIMIT_NOFILE), (5, 5))
         # If pid is 0 prlimit() applies to the calling process and
         # we don't want that.
-        with self.assertRaises(ValueError):
-            psutil._psplatform.Process(0).rlimit(0)
+        if LINUX:
+            with self.assertRaisesRegex(ValueError, "can't use prlimit"):
+                psutil._psplatform.Process(0).rlimit(0)
         with self.assertRaises(ValueError):
             p.rlimit(psutil.RLIMIT_NOFILE, (5, 5, 5))
 
diff --git a/scripts/procinfo.py b/scripts/procinfo.py
index f060538..8eea337 100755
--- a/scripts/procinfo.py
+++ b/scripts/procinfo.py
@@ -108,11 +108,14 @@
     "RLIMIT_NICE": "nice",
     "RLIMIT_NOFILE": "openfiles",
     "RLIMIT_NPROC": "maxprocesses",
+    "RLIMIT_NPTS": "pseudoterms",
     "RLIMIT_RSS": "rss",
     "RLIMIT_RTPRIO": "realtimeprio",
     "RLIMIT_RTTIME": "rtimesched",
+    "RLIMIT_SBSIZE": "sockbufsize",
     "RLIMIT_SIGPENDING": "sigspending",
     "RLIMIT_STACK": "stack",
+    "RLIMIT_SWAP": "swapuse",
 }
 
 
@@ -317,7 +320,7 @@
 def main(argv=None):
     parser = argparse.ArgumentParser(
         description="print information about a process")
-    parser.add_argument("pid", type=int, help="process pid")
+    parser.add_argument("pid", type=int, help="process pid", nargs='?')
     parser.add_argument('--verbose', '-v', action='store_true',
                         help="print more info")
     args = parser.parse_args()