From: Corinna Vinschen <[email protected]> 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 <[email protected]> --- 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, -- 2.52.0
