[newlib-cygwin/main] Cygwin: getrlimit/setrlimit: generalize setting rlimits
Corinna Vinschen
corinna@sourceware.org
Wed Mar 11 15:05:12 GMT 2026
https://sourceware.org/git/gitweb.cgi?p=newlib-cygwin.git;h=830159b313bd2587cb9d8fa9e3223fba562035ae
commit 830159b313bd2587cb9d8fa9e3223fba562035ae
Author: Corinna Vinschen <corinna@vinschen.de>
AuthorDate: Sun Jan 25 16:25:27 2026 +0100
Commit: Corinna Vinschen <corinna@vinschen.de>
CommitDate: Wed Mar 11 15:58:43 2026 +0100
Cygwin: getrlimit/setrlimit: generalize setting rlimits
Rewrite setting rlimits which are backed by Windows job objects.
That's namely RLIMIT_AS for now, a per-process limit. Prepare for
per-user limits like RLIMIT_NPROC, but don't implement them yet.
Basically store hard and soft limits in two adjacent job objects.
Create a new per-process job object for each new process.
Two new functions, __get_os_limits() and __set_os_limits() read and
create/write the Windows job object and assign the current process to
the job.
Two new child_info methods, collect_process_rlimits() and
inherit_process_rlimits() are used in fork/exec to hand down
the process limits to the child process.
Signed-off-by: Corinna Vinschen <corinna@vinschen.de>
Diff:
---
winsup/cygwin/dcrt0.cc | 3 +
winsup/cygwin/fork.cc | 4 +
winsup/cygwin/local_includes/child_info.h | 7 +-
winsup/cygwin/local_includes/cygheap.h | 1 -
winsup/cygwin/local_includes/ntdll.h | 1 +
winsup/cygwin/resource.cc | 177 +++++++++++++++++++++---------
winsup/cygwin/spawn.cc | 2 +
7 files changed, 141 insertions(+), 54 deletions(-)
diff --git a/winsup/cygwin/dcrt0.cc b/winsup/cygwin/dcrt0.cc
index 69c233c24759..c9fdcb4b3c72 100644
--- a/winsup/cygwin/dcrt0.cc
+++ b/winsup/cygwin/dcrt0.cc
@@ -894,6 +894,9 @@ dll_crt0_1 (void *)
uinfo_init (); /* initialize user info */
+ if (child_proc_info)
+ child_proc_info->inherit_process_rlimits ();
+
/* Connect to tty. */
tty::init_session ();
diff --git a/winsup/cygwin/fork.cc b/winsup/cygwin/fork.cc
index 7156fc31de5f..463fa54d0beb 100644
--- a/winsup/cygwin/fork.cc
+++ b/winsup/cygwin/fork.cc
@@ -152,6 +152,8 @@ frok::child (volatile char * volatile here)
clear_procimptoken ();
cygheap->user.reimpersonate ();
+ ch.inherit_process_rlimits ();
+
#ifdef DEBUGGING
if (GetEnvironmentVariableA ("FORKDEBUG", NULL, 0))
try_to_debug ();
@@ -317,6 +319,8 @@ frok::parent (volatile char * volatile stack_here)
debug_printf ("stack - bottom %p, top %p, addr %p, guardsize %ly",
ch.stackbase, ch.stacklimit, ch.stackaddr, ch.guardsize);
+ ch.collect_process_rlimits ();
+
PROCESS_INFORMATION pi;
STARTUPINFOW si;
diff --git a/winsup/cygwin/local_includes/child_info.h b/winsup/cygwin/local_includes/child_info.h
index dc0b75dee694..aaa3e936f229 100644
--- a/winsup/cygwin/local_includes/child_info.h
+++ b/winsup/cygwin/local_includes/child_info.h
@@ -33,7 +33,7 @@ enum child_status
#define EXEC_MAGIC_SIZE sizeof(child_info)
/* Change this value if you get a message indicating that it is out-of-sync. */
-#define CURR_CHILD_INFO_MAGIC 0x3c5c4429U
+#define CURR_CHILD_INFO_MAGIC 0x1052d96bU
#include "pinfo.h"
struct cchildren
@@ -68,6 +68,7 @@ public:
DWORD exit_code; // process exit code
static int retry_count;// retry count;
sigset_t sigmask;
+ struct rlimit rlimit_as;
child_info (unsigned, child_info_types, bool);
child_info (): subproc_ready (NULL), parent (NULL) {}
@@ -96,6 +97,10 @@ public:
else
flag &= ~_CI_SILENTFAIL;
}
+
+ /* resource.cc */
+ void collect_process_rlimits ();
+ void inherit_process_rlimits ();
};
class mount_info;
diff --git a/winsup/cygwin/local_includes/cygheap.h b/winsup/cygwin/local_includes/cygheap.h
index d9e936c1e469..74cfff65262a 100644
--- a/winsup/cygwin/local_includes/cygheap.h
+++ b/winsup/cygwin/local_includes/cygheap.h
@@ -517,7 +517,6 @@ public:
user_heap_info user_heap;
shared_region_info shared_regions;
mode_t umask;
- LONG rlim_as_id;
unsigned long rlim_core;
HANDLE console_h;
cwdstuff cwd;
diff --git a/winsup/cygwin/local_includes/ntdll.h b/winsup/cygwin/local_includes/ntdll.h
index e7afeb564afd..7c9fa2a8add5 100644
--- a/winsup/cygwin/local_includes/ntdll.h
+++ b/winsup/cygwin/local_includes/ntdll.h
@@ -1460,6 +1460,7 @@ extern "C"
PIO_STATUS_BLOCK, ULONG, PVOID, ULONG, PVOID,
ULONG);
NTSTATUS NtFlushBuffersFile (HANDLE, PIO_STATUS_BLOCK);
+ NTSTATUS NtIsProcessInJob (HANDLE, HANDLE);
NTSTATUS NtLockFile (HANDLE, HANDLE, PIO_APC_ROUTINE, PVOID, PIO_STATUS_BLOCK,
PLARGE_INTEGER, PLARGE_INTEGER, ULONG, BOOLEAN, BOOLEAN);
NTSTATUS NtLockVirtualMemory (HANDLE, PVOID *, PSIZE_T, ULONG);
diff --git a/winsup/cygwin/resource.cc b/winsup/cygwin/resource.cc
index 1e9e91810c8d..2286fe85052b 100644
--- a/winsup/cygwin/resource.cc
+++ b/winsup/cygwin/resource.cc
@@ -20,6 +20,7 @@ details. */
#include "pinfo.h"
#include "dtable.h"
#include "cygheap.h"
+#include "child_info.h"
#include "shared_info.h"
#include "ntdll.h"
@@ -165,66 +166,63 @@ get_rlimit_stack (void)
return (size_t) rl.rlim_cur;
}
-static LONG job_serial_number __attribute__((section (".cygwin_dll_common"), shared));
+enum limit_flags_t
+{
+ PER_PROCESS = 1,
+ PER_USER = 2,
+
+ SOFT_LIMIT = 4,
+ HARD_LIMIT = 8
+};
static PWCHAR
-job_shared_name (PWCHAR buf, LONG num)
+job_shared_name (PWCHAR buf, int flags)
{
- __small_swprintf (buf, L"rlimit.%d", num);
+ __small_swprintf (buf, L"rlimit.%C.%W.%u",
+ (flags & HARD_LIMIT) ? L'H' : L'S',
+ (flags & PER_USER) ? L"uid" : L"pid",
+ (flags & PER_USER) ? getuid () : getpid ());
return buf;
}
-static void
-__get_rlimit_as (struct rlimit *rlp)
+static PJOBOBJECT_EXTENDED_LIMIT_INFORMATION
+__get_os_limits (JOBOBJECT_EXTENDED_LIMIT_INFORMATION &jobinfo, int flags)
{
+ OBJECT_ATTRIBUTES attr;
UNICODE_STRING uname;
WCHAR jobname[32];
- OBJECT_ATTRIBUTES attr;
HANDLE job = NULL;
NTSTATUS status;
- JOBOBJECT_EXTENDED_LIMIT_INFORMATION jobinfo;
- if (cygheap->rlim_as_id)
- {
- RtlInitUnicodeString (&uname,
- job_shared_name (jobname,
- cygheap->rlim_as_id));
- InitializeObjectAttributes (&attr, &uname, 0,
- get_session_parent_dir (), NULL);
- /* May fail, just check NULL job in that case. */
- NtOpenJobObject (&job, JOB_OBJECT_QUERY, &attr);
- }
+ RtlInitUnicodeString (&uname, job_shared_name (jobname, flags));
+ InitializeObjectAttributes (&attr, &uname, 0,
+ flags & PER_USER ? get_shared_parent_dir ()
+ : get_session_parent_dir (),
+ NULL);
+ /* May fail, just check NULL job in that case. */
+ NtOpenJobObject (&job, JOB_OBJECT_QUERY, &attr);
status = NtQueryInformationJobObject (job,
- JobObjectExtendedLimitInformation,
- &jobinfo, sizeof jobinfo, NULL);
- if (NT_SUCCESS (status)
- && (jobinfo.BasicLimitInformation.LimitFlags
- & JOB_OBJECT_LIMIT_PROCESS_MEMORY))
- rlp->rlim_cur = rlp->rlim_max = jobinfo.ProcessMemoryLimit;
+ JobObjectExtendedLimitInformation,
+ &jobinfo, sizeof jobinfo, NULL);
if (job)
NtClose (job);
+ return NT_SUCCESS (status) ? &jobinfo : NULL;
}
static int
-__set_rlimit_as (unsigned long new_as_limit)
+__set_os_limits (JOBOBJECT_EXTENDED_LIMIT_INFORMATION &jobinfo, int flags)
{
- LONG new_as_id = 0;
+ NTSTATUS status = STATUS_SUCCESS;
+ OBJECT_ATTRIBUTES attr;
UNICODE_STRING uname;
WCHAR jobname[32];
- OBJECT_ATTRIBUTES attr;
- NTSTATUS status = STATUS_SUCCESS;
HANDLE job = NULL;
- JOBOBJECT_EXTENDED_LIMIT_INFORMATION jobinfo = { 0 };
- /* If we already have a limit, we must not change it because that
- would potentially influence already running child processes.
- Just try to create another, nested job. */
- while (new_as_id == 0)
- new_as_id = InterlockedIncrement (&job_serial_number);
- RtlInitUnicodeString (&uname,
- job_shared_name (jobname, new_as_id));
- InitializeObjectAttributes (&attr, &uname, 0,
- get_session_parent_dir (), NULL);
+ RtlInitUnicodeString (&uname, job_shared_name (jobname, flags));
+ InitializeObjectAttributes (&attr, &uname, OBJ_OPENIF,
+ flags & PER_USER ? get_shared_parent_dir ()
+ : get_session_parent_dir (),
+ NULL);
status = NtCreateJobObject (&job, JOB_OBJECT_ALL_ACCESS, &attr);
if (!NT_SUCCESS (status))
{
@@ -232,28 +230,104 @@ __set_rlimit_as (unsigned long new_as_limit)
return -1;
}
jobinfo.BasicLimitInformation.LimitFlags
- = JOB_OBJECT_LIMIT_PROCESS_MEMORY;
- /* Per Linux man page, round down to system pagesize. */
- jobinfo.ProcessMemoryLimit
- = rounddown (new_as_limit, wincap.allocation_granularity ());
+ |= JOB_OBJECT_LIMIT_BREAKAWAY_OK;
+ /* Every process gets its own per-process jobs, so always breakaway
+ silently from per-process jobs. */
+ if (flags & PER_PROCESS)
+ jobinfo.BasicLimitInformation.LimitFlags
+ |= JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK;
status = NtSetInformationJobObject (job,
- JobObjectExtendedLimitInformation,
- &jobinfo, sizeof jobinfo);
- /* If creating the job and setting up the job limits succeeded,
- try to add the process to the job. This must be the last step,
- otherwise we couldn't remove the job if anything failed. */
- if (NT_SUCCESS (status))
- status = NtAssignProcessToJobObject (job, NtCurrentProcess ());
- NtClose (job);
+ JobObjectExtendedLimitInformation,
+ &jobinfo, sizeof jobinfo);
+ /* Assign the process to the job if it's not already assigned. */
+ NTSTATUS in_job = NtIsProcessInJob (NtCurrentProcess (), job);
+ if (NT_SUCCESS (status) && in_job == STATUS_PROCESS_NOT_IN_JOB)
+ {
+ status = NtAssignProcessToJobObject (job, NtCurrentProcess ());
+ if (!NT_SUCCESS (status))
+ debug_printf ("NtAssignProcessToJobObject: %y\r", status);
+ }
+
+ /* Keep the handle ONLY if we just assigned the process to the job */
+ if (!NT_SUCCESS (status) || in_job == STATUS_PROCESS_IN_JOB)
+ NtClose (job);
+
if (!NT_SUCCESS (status))
{
__seterrno_from_nt_status (status);
return -1;
}
- cygheap->rlim_as_id = new_as_id;
return 0;
}
+static void
+__get_rlimit_as (struct rlimit *rlp)
+{
+ JOBOBJECT_EXTENDED_LIMIT_INFORMATION jobinfo;
+
+ rlp->rlim_cur = RLIM_INFINITY;
+ rlp->rlim_max = RLIM_INFINITY;
+ if (__get_os_limits (jobinfo, PER_PROCESS | HARD_LIMIT)
+ && (jobinfo.BasicLimitInformation.LimitFlags
+ & JOB_OBJECT_LIMIT_PROCESS_MEMORY))
+ rlp->rlim_max = jobinfo.ProcessMemoryLimit;
+ if (__get_os_limits (jobinfo, PER_PROCESS | SOFT_LIMIT)
+ && (jobinfo.BasicLimitInformation.LimitFlags
+ & JOB_OBJECT_LIMIT_PROCESS_MEMORY))
+ rlp->rlim_cur = jobinfo.ProcessMemoryLimit;
+}
+
+static int
+__set_rlimit_as_single (rlim_t val, int flags)
+{
+ JOBOBJECT_EXTENDED_LIMIT_INFORMATION jobinfo = { 0 };
+
+ __get_os_limits (jobinfo, PER_PROCESS | flags);
+ if (val == RLIM_INFINITY)
+ {
+ jobinfo.BasicLimitInformation.LimitFlags
+ &= ~JOB_OBJECT_LIMIT_PROCESS_MEMORY;
+ jobinfo.ProcessMemoryLimit = 0;
+ }
+ else /* Per Linux man page, round down to system pagesize. */
+ {
+ jobinfo.BasicLimitInformation.LimitFlags
+ |= JOB_OBJECT_LIMIT_PROCESS_MEMORY;
+ jobinfo.ProcessMemoryLimit
+ = rounddown (val, wincap.allocation_granularity ());
+ }
+ return __set_os_limits (jobinfo, PER_PROCESS | flags);
+}
+
+static int
+__set_rlimit_as (const struct rlimit *rlp)
+{
+ int ret = __set_rlimit_as_single (rlp->rlim_max, HARD_LIMIT);
+ if (ret == 0)
+ ret = __set_rlimit_as_single (rlp->rlim_cur, SOFT_LIMIT);
+ return ret;
+}
+
+/* Called during fork/exec in the parent to collect the per-process rlimits. */
+void
+child_info::collect_process_rlimits ()
+{
+ __get_rlimit_as (&rlimit_as);
+ debug_printf ("parent rlimit AS: max %U cur %U",
+ rlimit_as.rlim_max, rlimit_as.rlim_cur);
+}
+
+/* Called during fork/exec in the child to duplicate the per-process rlimits. */
+void
+child_info::inherit_process_rlimits ()
+{
+ if (rlimit_as.rlim_max != RLIM_INFINITY
+ || rlimit_as.rlim_cur != RLIM_INFINITY)
+ __set_rlimit_as (&rlimit_as);
+ debug_printf ("child rlimit AS: max %U cur %U",
+ rlimit_as.rlim_max, rlimit_as.rlim_cur);
+}
+
extern "C" int
getrlimit (int resource, struct rlimit *rlp)
{
@@ -325,8 +399,7 @@ setrlimit (int resource, const struct rlimit *rlp)
switch (resource)
{
case RLIMIT_AS:
- if (rlp->rlim_cur != RLIM_INFINITY)
- return __set_rlimit_as (rlp->rlim_cur);
+ return __set_rlimit_as (rlp);
break;
case RLIMIT_CORE:
cygheap->rlim_core = rlp->rlim_cur;
diff --git a/winsup/cygwin/spawn.cc b/winsup/cygwin/spawn.cc
index 04e4a4028b8a..f7ed9cf4f70b 100644
--- a/winsup/cygwin/spawn.cc
+++ b/winsup/cygwin/spawn.cc
@@ -571,6 +571,8 @@ child_info_spawn::worker (const char *prog_arg, const char *const *argv,
SetHandleInformation (my_wr_proc_pipe, HANDLE_FLAG_INHERIT, 0);
parent_winpid = GetCurrentProcessId ();
+ collect_process_rlimits ();
+
PSECURITY_ATTRIBUTES sa = (PSECURITY_ATTRIBUTES) alloca (1024);
if (!sec_user_nih (sa, cygheap->user.sid (),
well_known_authenticated_users_sid,
More information about the Cygwin-cvs
mailing list