From 0caf7472a129b1f82623f8348a66b834426d07a3 Mon Sep 17 00:00:00 2001
From: Omair Majid <omajid@redhat.com>
Date: Oct 28 2019 22:31:03 +0000
Subject: Add cgroupv2 support to .NET Core


---

diff --git a/coreclr-cgroupv2-02.patch b/coreclr-cgroupv2-02.patch
new file mode 100644
index 0000000..3097d86
--- /dev/null
+++ b/coreclr-cgroupv2-02.patch
@@ -0,0 +1,54 @@
+diff --git a/src/gc/unix/cgroup.cpp b/src/gc/unix/cgroup.cpp
+index e5760bebd8..d9a4e2dedd 100644
+--- a/src/gc/unix/cgroup.cpp
++++ b/src/gc/unix/cgroup.cpp
+@@ -31,6 +31,9 @@ Abstract:
+ #ifndef SIZE_T_MAX
+ #define SIZE_T_MAX (~(size_t)0)
+ #endif
++#ifndef _countof
++#define _countof(_array) (sizeof(_array) / sizeof(_array[0]))
++#endif // !_countof
+ 
+ #define PROC_MOUNTINFO_FILENAME "/proc/self/mountinfo"
+ #define PROC_CGROUP_FILENAME "/proc/self/cgroup"
+@@ -74,7 +77,7 @@ public:
+         if (s_memory_cgroup_path == nullptr)
+             return result;
+ 
+-        mem_limit_filename = SearchForFile(s_memory_cgroup_path, files, sizeof(files));
++        mem_limit_filename = SearchForFile(s_memory_cgroup_path, files, _countof(files));
+         if (mem_limit_filename == nullptr)
+             return result;
+ 
+@@ -97,7 +100,7 @@ public:
+         if (s_memory_cgroup_path == nullptr)
+             return result;
+ 
+-        mem_usage_filename = SearchForFile(s_memory_cgroup_path, files, sizeof(files));
++        mem_usage_filename = SearchForFile(s_memory_cgroup_path, files, _countof(files));
+         if (mem_usage_filename == nullptr)
+             return result;
+ 
+diff --git a/src/pal/src/misc/cgroup.cpp b/src/pal/src/misc/cgroup.cpp
+index e32b74aabe..0b922123c8 100644
+--- a/src/pal/src/misc/cgroup.cpp
++++ b/src/pal/src/misc/cgroup.cpp
+@@ -62,7 +61,7 @@ public:
+         if (s_memory_cgroup_path == nullptr)
+             return result;
+ 
+-        mem_limit_filename = SearchForFile(s_memory_cgroup_path, files, sizeof(files));
++        mem_limit_filename = SearchForFile(s_memory_cgroup_path, files, _countof(files));
+         if (mem_limit_filename == nullptr)
+             return result;
+ 
+@@ -85,7 +84,7 @@ public:
+         if (s_memory_cgroup_path == nullptr)
+             return result;
+ 
+-        mem_usage_filename = SearchForFile(s_memory_cgroup_path, files, sizeof(files));
++        mem_usage_filename = SearchForFile(s_memory_cgroup_path, files, _countof(files));
+         if (mem_usage_filename == nullptr)
+             return result;
+ 
diff --git a/coreclr-cgroupv2.patch b/coreclr-cgroupv2.patch
new file mode 100644
index 0000000..2233808
--- /dev/null
+++ b/coreclr-cgroupv2.patch
@@ -0,0 +1,876 @@
+From 7b33e078b41819284175533215047359bb952b5d Mon Sep 17 00:00:00 2001
+From: Omair Majid <omajid@redhat.com>
+Date: Fri, 26 Jul 2019 17:12:48 -0400
+Subject: [PATCH] Add cgroup v2 support to CoreCLR
+
+Upstream cgroup v2 documentation is available at:
+https://www.kernel.org/doc/Documentation/cgroup-v2.txt
+
+Some notable differences between cgroup v1 and v2, from a CoreCLR point
+of view, include:
+
+- cgroup v2 has a single hierarchy, so we just look for a single "cgroup2"
+  entry in /proc/self/mountinfo (without looking for a subsystem match).
+
+- Since cgroup v2 has a single hierarchy, /proc/self/cgroup generally
+  has a single line "0::/path". There's no need to match subsystems or
+  hierarchy ids here.
+
+- "memory.limit_in_bytes" is now "memory.max". It can contain the
+  literal "max" to indicate no limit.
+
+- "memory.usage_in_bytes" is now "memory.current"
+
+- "cpu.cfs_quota_us" and "cpu.cfs_period_us" have been combined into a
+  single "cpu.max" file with the format "$MAX $PERIOD". The max value
+  can be a literal "max" to indicate a limit is not active.
+---
+ src/gc/unix/cgroup.cpp      | 292 +++++++++++++++++++++++++++++-------
+ src/pal/src/misc/cgroup.cpp | 287 ++++++++++++++++++++++++++++-------
+ 2 files changed, 471 insertions(+), 108 deletions(-)
+
+diff --git a/src/gc/unix/cgroup.cpp b/src/gc/unix/cgroup.cpp
+index 1e60898948..e5760bebd8 100644
+--- a/src/gc/unix/cgroup.cpp
++++ b/src/gc/unix/cgroup.cpp
+@@ -35,20 +35,24 @@ Abstract:
+ #define PROC_MOUNTINFO_FILENAME "/proc/self/mountinfo"
+ #define PROC_CGROUP_FILENAME "/proc/self/cgroup"
+ #define PROC_STATM_FILENAME "/proc/self/statm"
+-#define MEM_LIMIT_FILENAME "/memory.limit_in_bytes"
+-#define MEM_USAGE_FILENAME "/memory.usage_in_bytes"
+-#define CFS_QUOTA_FILENAME "/cpu.cfs_quota_us"
+-#define CFS_PERIOD_FILENAME "/cpu.cfs_period_us"
++#define CGROUP1_MEMORY_LIMIT_FILENAME "/memory.limit_in_bytes"
++#define CGROUP2_MEMORY_LIMIT_FILENAME "/memory.max"
++#define CGROUP1_MEMORY_USAGE_FILENAME "/memory.usage_in_bytes"
++#define CGROUP2_MEMORY_USAGE_FILENAME "/memory.current"
++#define CGROUP1_CFS_QUOTA_FILENAME "/cpu.cfs_quota_us"
++#define CGROUP1_CFS_PERIOD_FILENAME "/cpu.cfs_period_us"
++#define CGROUP2_CPU_MAX_FILENAME "/cpu.max"
++
+ 
+ class CGroup
+ {
+-    static char* s_memory_cgroup_path;
+-    static char* s_cpu_cgroup_path;
++    static char *s_memory_cgroup_path;
++    static char *s_cpu_cgroup_path;
+ public:
+     static void Initialize()
+     {
+-        s_memory_cgroup_path = FindCgroupPath(&IsMemorySubsystem);
+-        s_cpu_cgroup_path = FindCgroupPath(&IsCpuSubsystem);
++        s_memory_cgroup_path = FindCGroupPath(&IsCGroup1MemorySubsystem);
++        s_cpu_cgroup_path = FindCGroupPath(&IsCGroup1CpuSubsystem);
+     }
+ 
+     static void Cleanup()
+@@ -59,20 +63,21 @@ public:
+ 
+     static bool GetPhysicalMemoryLimit(size_t *val)
+     {
++        const char *files[] = {
++            CGROUP1_MEMORY_LIMIT_FILENAME,
++            CGROUP2_MEMORY_LIMIT_FILENAME
++        };
++
+         char *mem_limit_filename = nullptr;
+         bool result = false;
+ 
+         if (s_memory_cgroup_path == nullptr)
+             return result;
+ 
+-        size_t len = strlen(s_memory_cgroup_path);
+-        len += strlen(MEM_LIMIT_FILENAME);
+-        mem_limit_filename = (char*)malloc(len+1);
++        mem_limit_filename = SearchForFile(s_memory_cgroup_path, files, sizeof(files));
+         if (mem_limit_filename == nullptr)
+             return result;
+ 
+-        strcpy(mem_limit_filename, s_memory_cgroup_path);
+-        strcat(mem_limit_filename, MEM_LIMIT_FILENAME);
+         result = ReadMemoryValueFromFile(mem_limit_filename, val);
+         free(mem_limit_filename);
+         return result;
+@@ -80,6 +85,11 @@ public:
+ 
+     static bool GetPhysicalMemoryUsage(size_t *val)
+     {
++        const char *files[] = {
++            CGROUP1_MEMORY_USAGE_FILENAME,
++            CGROUP2_MEMORY_USAGE_FILENAME
++        };
++
+         char *mem_usage_filename = nullptr;
+         bool result = false;
+ 
+@@ -87,14 +97,10 @@ public:
+         if (s_memory_cgroup_path == nullptr)
+             return result;
+ 
+-        size_t len = strlen(s_memory_cgroup_path);
+-        len += strlen(MEM_USAGE_FILENAME);
+-        mem_usage_filename = (char*)malloc(len+1);
++        mem_usage_filename = SearchForFile(s_memory_cgroup_path, files, sizeof(files));
+         if (mem_usage_filename == nullptr)
+             return result;
+ 
+-        strcpy(mem_usage_filename, s_memory_cgroup_path);
+-        strcat(mem_usage_filename, MEM_USAGE_FILENAME);
+         result = ReadMemoryValueFromFile(mem_usage_filename, val);
+         free(mem_usage_filename);
+         return result;
+@@ -112,16 +118,149 @@ public:
+     }
+ 
+     static bool GetCpuLimit(uint32_t *val)
++    {
++        if (GetCGroup1CpuLimit(val))
++        {
++            return true;
++        }
++
++        if (GetCGroup2CpuLimit(val))
++        {
++            return true;
++        }
++
++        return false;
++    }
++
++private:
++    static bool IsCGroup1MemorySubsystem(const char *strTok){
++        return strcmp("memory", strTok) == 0;
++    }
++
++    static bool IsCGroup1CpuSubsystem(const char *strTok){
++        return strcmp("cpu", strTok) == 0;
++    }
++
++    static bool GetCGroup2CpuLimit(uint32_t *val)
++    {
++        char *line = nullptr;
++        char *filename = nullptr;
++        FILE *file = nullptr;
++        char *max_quota_string = nullptr;
++        char *period_string = nullptr;
++
++        size_t lineLen = 0;
++        int sscanRet = 0;
++        long long max_quota = 0;
++        long long period = 0;
++        double cpu_count = 0;
++
++        bool result = false;
++
++        if (s_cpu_cgroup_path == nullptr)
++        {
++            return false;
++        }
++
++        size_t filename_len = strlen(s_cpu_cgroup_path) + strlen(CGROUP2_CPU_MAX_FILENAME);
++        filename = (char*)malloc(filename_len + 1);
++        if (filename == nullptr)
++        {
++            goto done;
++        }
++
++        strcpy(filename, s_cpu_cgroup_path);
++        strcat(filename, CGROUP2_CPU_MAX_FILENAME);
++
++        file = fopen(filename, "r");
++        if (file == nullptr)
++        {
++            goto done;
++        }
++
++        if (getline(&line, &lineLen, file) == -1)
++        {
++            goto done;
++        }
++
++        // The expected format is:
++        //     $MAX $PERIOD
++        // Where "$MAX" may be the string literal "max"
++
++        max_quota_string = (char*) malloc(lineLen + 1);
++        if (max_quota_string == nullptr)
++        {
++            goto done;
++        }
++        period_string = (char*) malloc(lineLen + 1);
++        if (period_string == nullptr)
++        {
++            goto done;
++        }
++
++        sscanRet = sscanf(line, "%s %s", max_quota_string, period_string);
++        if (sscanRet != 2)
++        {
++            assert(!"Unable to parse " CGROUP2_CPU_MAX_FILENAME " file contents with sscanf.");
++            goto done;
++        }
++
++        // "max" means no cpu limit
++        if (strncmp("max", max_quota_string, lineLen + 1) == 0)
++        {
++            goto done;
++        }
++
++        errno = 0;
++        max_quota = atoll(max_quota_string);
++        if (errno != 0)
++        {
++            goto done;
++        }
++
++        errno = 0;
++        period = atoll(period_string);
++        if (errno != 0)
++        {
++            goto done;
++        }
++
++        // Cannot have less than 1 CPU
++        if (max_quota <= period)
++        {
++            result = true;
++            *val = 1;
++            goto done;
++        }
++
++        // Calculate cpu count based on quota and round it up
++        cpu_count = (double) max_quota / period  + 0.999999999;
++        *val = (cpu_count < UINT32_MAX) ? (uint32_t)cpu_count : UINT32_MAX;
++
++        result = true;
++
++    done:
++        if (file)
++            fclose(file);
++        free(filename);
++        free(line);
++        free(max_quota_string);
++        free(period_string);
++
++        return result;
++    }
++
++    static bool GetCGroup1CpuLimit(uint32_t *val)
+     {
+         long long quota;
+         long long period;
+         double cpu_count;
+ 
+-        quota = ReadCpuCGroupValue(CFS_QUOTA_FILENAME);
++        quota = ReadCpuCGroupValue(CGROUP1_CFS_QUOTA_FILENAME);
+         if (quota <= 0)
+             return false;
+ 
+-        period = ReadCpuCGroupValue(CFS_PERIOD_FILENAME);
++        period = ReadCpuCGroupValue(CGROUP1_CFS_PERIOD_FILENAME);
+         if (period <= 0)
+             return false;
+ 
+@@ -138,27 +277,19 @@ public:
+ 
+         return true;
+     }
+-    
+-private:
+-    static bool IsMemorySubsystem(const char *strTok){
+-        return strcmp("memory", strTok) == 0;
+-    }
+ 
+-    static bool IsCpuSubsystem(const char *strTok){
+-        return strcmp("cpu", strTok) == 0;
+-    }
+-
+-    static char* FindCgroupPath(bool (*is_subsystem)(const char *)){
++    static char* FindCGroupPath(bool (*is_subsystem)(const char *)){
+         char *cgroup_path = nullptr;
+         char *hierarchy_mount = nullptr;
+         char *hierarchy_root = nullptr;
+         char *cgroup_path_relative_to_mount = nullptr;
++        bool is_cgroupv2 = false;
+ 
+-        FindHierarchyMount(is_subsystem, &hierarchy_mount, &hierarchy_root);
++        FindHierarchyMount(is_subsystem, &is_cgroupv2, &hierarchy_mount, &hierarchy_root);
+         if (hierarchy_mount == nullptr || hierarchy_root == nullptr)
+             goto done;
+ 
+-        cgroup_path_relative_to_mount = FindCGroupPathForSubsystem(is_subsystem);
++        cgroup_path_relative_to_mount = FindCGroupPathForSubsystem(is_cgroupv2, is_subsystem);
+         if (cgroup_path_relative_to_mount == nullptr)
+             goto done;
+ 
+@@ -179,7 +310,7 @@ private:
+         return cgroup_path;
+     }
+ 
+-    static void FindHierarchyMount(bool (*is_subsystem)(const char *), char** pmountpath, char** pmountroot)
++    static void FindHierarchyMount(bool (*is_subsystem)(const char *), bool* is_cgroupv2, char** pmountpath, char** pmountroot)
+     {
+         char *line = nullptr;
+         size_t lineLen = 0, maxLineLen = 0;
+@@ -219,14 +350,18 @@ private:
+                 assert(!"Failed to parse mount info file contents with sscanf.");
+                 goto done;
+             }
+-    
++
+             if (strncmp(filesystemType, "cgroup", 6) == 0)
+             {
++                if (strncmp(filesystemType, "cgroup2", 8) == 0)
++                {
++                    *is_cgroupv2 = true;
++                }
+                 char* context = nullptr;
+                 char* strTok = strtok_r(options, ",", &context); 
+                 while (strTok != nullptr)
+                 {
+-                    if (is_subsystem(strTok))
++                    if (*is_cgroupv2 || is_subsystem(strTok))
+                     {
+                         mountpath = (char*)malloc(lineLen+1);
+                         if (mountpath == nullptr)
+@@ -234,7 +369,7 @@ private:
+                         mountroot = (char*)malloc(lineLen+1);
+                         if (mountroot == nullptr)
+                             goto done;
+-    
++
+                         sscanfRet = sscanf(line,
+                                            "%*s %*s %*s %s %s ",
+                                            mountroot,
+@@ -261,8 +396,8 @@ private:
+         if (mountinfofile)
+             fclose(mountinfofile);
+     }
+-    
+-    static char* FindCGroupPathForSubsystem(bool (*is_subsystem)(const char *))
++
++    static char* FindCGroupPathForSubsystem(bool is_cgroupv2, bool (*is_subsystem)(const char *))
+     {
+         char *line = nullptr;
+         size_t lineLen = 0;
+@@ -274,7 +409,7 @@ private:
+         FILE *cgroupfile = fopen(PROC_CGROUP_FILENAME, "r");
+         if (cgroupfile == nullptr)
+             goto done;
+-    
++
+         while (!result && getline(&line, &lineLen, cgroupfile) != -1)
+         {
+             if (subsystem_list == nullptr || lineLen > maxLineLen)
+@@ -289,28 +424,43 @@ private:
+                     goto done;
+                 maxLineLen = lineLen;
+             }
+-                   
+-            // See man page of proc to get format for /proc/self/cgroup file
+-            int sscanfRet = sscanf(line, 
+-                                   "%*[^:]:%[^:]:%s",
+-                                   subsystem_list,
+-                                   cgroup_path);
+-            if (sscanfRet != 2)
++
++            if (is_cgroupv2)
+             {
+-                assert(!"Failed to parse cgroup info file contents with sscanf.");
+-                goto done;
++                // See https://www.kernel.org/doc/Documentation/cgroup-v2.txt
++                // Look for "0::/some/path"
++                int sscanfRet = sscanf(line,
++                                       "0::%s",
++                                       cgroup_path);
++                if (sscanfRet == 1)
++                {
++                    result = true;
++                }
+             }
+-    
+-            char* context = nullptr;
+-            char* strTok = strtok_r(subsystem_list, ",", &context); 
+-            while (strTok != nullptr)
++            else
+             {
+-                if (is_subsystem(strTok))
++                // See man page of proc to get format for /proc/self/cgroup file
++                int sscanfRet = sscanf(line,
++                                       "%*[^:]:%[^:]:%s",
++                                       subsystem_list,
++                                       cgroup_path);
++                if (sscanfRet != 2)
+                 {
+-                    result = true;
+-                    break;  
++                    assert(!"Failed to parse cgroup info file contents with sscanf.");
++                    goto done;
++                }
++
++                char* context = nullptr;
++                char* strTok = strtok_r(subsystem_list, ",", &context);
++                while (strTok != nullptr)
++                {
++                    if (is_subsystem(strTok))
++                    {
++                        result = true;
++                        break;
++                    }
++                    strTok = strtok_r(nullptr, ",", &context);
+                 }
+-                strTok = strtok_r(nullptr, ",", &context);
+             }
+         }
+     done:
+@@ -325,6 +475,36 @@ private:
+             fclose(cgroupfile);
+         return cgroup_path;
+     }
++
++    static char* SearchForFile(char* search_root, const char* possible_files[], size_t possible_files_len)
++    {
++        if (search_root == nullptr)
++        {
++            return nullptr;
++        }
++
++        for (size_t i = 0; i < possible_files_len; i++)
++        {
++            const char* possible_file = possible_files[i];
++            size_t len = strlen(search_root);
++            len += strlen(possible_file);
++            char* full_filename = (char*)malloc(len+1);
++            if (full_filename == nullptr)
++            {
++                return nullptr;
++            }
++            strcpy(full_filename, search_root);
++            strcat(full_filename, possible_file);
++            if (access(full_filename, R_OK) != -1)
++            {
++                return full_filename;
++            }
++
++            free(full_filename);
++        }
++
++        return nullptr;
++    }
+     
+     static bool ReadMemoryValueFromFile(const char* filename, size_t* val)
+     {
+diff --git a/src/pal/src/misc/cgroup.cpp b/src/pal/src/misc/cgroup.cpp
+index 3fc43a7b05..e32b74aabe 100644
+--- a/src/pal/src/misc/cgroup.cpp
++++ b/src/pal/src/misc/cgroup.cpp
+@@ -23,10 +23,15 @@
+ #define PROC_MOUNTINFO_FILENAME "/proc/self/mountinfo"
+ #define PROC_CGROUP_FILENAME "/proc/self/cgroup"
+ #define PROC_STATM_FILENAME "/proc/self/statm"
+-#define MEM_LIMIT_FILENAME "/memory.limit_in_bytes"
+-#define MEM_USAGE_FILENAME "/memory.usage_in_bytes"
+-#define CFS_QUOTA_FILENAME "/cpu.cfs_quota_us"
+-#define CFS_PERIOD_FILENAME "/cpu.cfs_period_us"
++#define CGROUP1_MEMORY_LIMIT_FILENAME "/memory.limit_in_bytes"
++#define CGROUP2_MEMORY_LIMIT_FILENAME "/memory.max"
++#define CGROUP1_MEMORY_USAGE_FILENAME "/memory.usage_in_bytes"
++#define CGROUP2_MEMORY_USAGE_FILENAME "/memory.current"
++#define CGROUP1_CFS_QUOTA_FILENAME "/cpu.cfs_quota_us"
++#define CGROUP1_CFS_PERIOD_FILENAME "/cpu.cfs_period_us"
++#define CGROUP2_CPU_MAX_FILENAME "/cpu.max"
++
++
+ class CGroup
+ {
+     static char *s_memory_cgroup_path;
+@@ -34,8 +39,8 @@
+ public:
+     static void Initialize()
+     {
+-        s_memory_cgroup_path = FindCgroupPath(&IsMemorySubsystem);
+-        s_cpu_cgroup_path = FindCgroupPath(&IsCpuSubsystem);
++        s_memory_cgroup_path = FindCGroupPath(&IsCGroup1MemorySubsystem);
++        s_cpu_cgroup_path = FindCGroupPath(&IsCGroup1CpuSubsystem);
+     }
+ 
+     static void Cleanup()
+@@ -43,23 +48,24 @@
+         PAL_free(s_memory_cgroup_path);
+         PAL_free(s_cpu_cgroup_path);
+     }
+-    
++
+     static bool GetPhysicalMemoryLimit(size_t *val)
+     {
++        const char *files[] = {
++            CGROUP1_MEMORY_LIMIT_FILENAME,
++            CGROUP2_MEMORY_LIMIT_FILENAME
++        };
++
+         char *mem_limit_filename = nullptr;
+         bool result = false;
+ 
+         if (s_memory_cgroup_path == nullptr)
+             return result;
+ 
+-        size_t len = strlen(s_memory_cgroup_path);
+-        len += strlen(MEM_LIMIT_FILENAME);
+-        mem_limit_filename = (char*)PAL_malloc(len+1);
++        mem_limit_filename = SearchForFile(s_memory_cgroup_path, files, sizeof(files));
+         if (mem_limit_filename == nullptr)
+             return result;
+ 
+-        strcpy_s(mem_limit_filename, len+1, s_memory_cgroup_path);
+-        strcat_s(mem_limit_filename, len+1, MEM_LIMIT_FILENAME);
+         result = ReadMemoryValueFromFile(mem_limit_filename, val);
+         PAL_free(mem_limit_filename);
+         return result;
+@@ -67,36 +73,172 @@
+ 
+     static bool GetPhysicalMemoryUsage(size_t *val)
+     {
++        const char *files[] = {
++            CGROUP1_MEMORY_USAGE_FILENAME,
++            CGROUP2_MEMORY_USAGE_FILENAME
++        };
++
+         char *mem_usage_filename = nullptr;
+         bool result = false;
+ 
+         if (s_memory_cgroup_path == nullptr)
+             return result;
+ 
+-        size_t len = strlen(s_memory_cgroup_path);
+-        len += strlen(MEM_USAGE_FILENAME);
+-        mem_usage_filename = (char*)malloc(len+1);
++        mem_usage_filename = SearchForFile(s_memory_cgroup_path, files, sizeof(files));
+         if (mem_usage_filename == nullptr)
+             return result;
+ 
+-        strcpy(mem_usage_filename, s_memory_cgroup_path);
+-        strcat(mem_usage_filename, MEM_USAGE_FILENAME);
+         result = ReadMemoryValueFromFile(mem_usage_filename, val);
+-        free(mem_usage_filename);
++        PAL_free(mem_usage_filename);
+         return result;
+     }
+ 
+     static bool GetCpuLimit(UINT *val)
+     {
++        if (GetCGroup1CpuLimit(val))
++        {
++            return true;
++        }
++
++        if (GetCGroup2CpuLimit(val))
++        {
++            return true;
++        }
++
++        return false;
++    }
++
++private:
++    static bool IsCGroup1MemorySubsystem(const char *strTok){
++        return strcmp("memory", strTok) == 0;
++    }
++
++    static bool IsCGroup1CpuSubsystem(const char *strTok){
++        return strcmp("cpu", strTok) == 0;
++    }
++
++    static bool GetCGroup2CpuLimit(UINT *val)
++    {
++        char *line = nullptr;
++        char *filename = nullptr;
++        FILE *file = nullptr;
++        char *max_quota_string = nullptr;
++        char *period_string = nullptr;
++
++        size_t lineLen = 0;
++        int sscanRet = 0;
++        long long max_quota = 0;
++        long long period = 0;
++        double cpu_count = 0;
++
++        bool result = false;
++
++        if (s_cpu_cgroup_path == nullptr)
++        {
++            return false;
++        }
++
++        size_t filename_len = strlen(s_cpu_cgroup_path) + strlen(CGROUP2_CPU_MAX_FILENAME);
++        filename = (char*)PAL_malloc(filename_len + 1);
++        if (filename == nullptr)
++        {
++            goto done;
++        }
++
++        strcpy_s(filename, filename_len + 1, s_cpu_cgroup_path);
++        strcat_s(filename, filename_len + 1, CGROUP2_CPU_MAX_FILENAME);
++
++        file = fopen(filename, "r");
++        if (file == nullptr)
++        {
++            goto done;
++        }
++
++        if (getline(&line, &lineLen, file) == -1)
++        {
++            goto done;
++        }
++
++        // The expected format is:
++        //     $MAX $PERIOD
++        // Where "$MAX" may be the string literal "max"
++
++        max_quota_string = (char*)PAL_malloc(lineLen + 1);
++        if (max_quota_string == nullptr)
++        {
++            goto done;
++        }
++        period_string = (char*)PAL_malloc(lineLen + 1);
++        if (period_string == nullptr)
++        {
++            goto done;
++        }
++
++        sscanRet = sscanf_s(line, "%s %s",
++                            max_quota_string, lineLen + 1,
++                            period_string, lineLen + 1);
++        if (sscanRet != 2)
++        {
++            _ASSERTE(!"Unable to parse " CGROUP2_CPU_MAX_FILENAME " file contents with sscanf_s.");
++            goto done;
++        }
++
++        // "max" means no cpu limit
++        if (strncmp("max", max_quota_string, lineLen + 1) == 0)
++        {
++            goto done;
++        }
++
++        errno = 0;
++        max_quota = atoll(max_quota_string);
++        if (errno != 0)
++        {
++            goto done;
++        }
++
++        errno = 0;
++        period = atoll(period_string);
++        if (errno != 0)
++        {
++            goto done;
++        }
++
++        // Cannot have less than 1 CPU
++        if (max_quota <= period)
++        {
++            result = true;
++            *val = 1;
++            goto done;
++        }
++
++        // Calculate cpu count based on quota and round it up
++        cpu_count = (double) max_quota / period  + 0.999999999;
++        *val = (cpu_count < UINT_MAX) ? (UINT)cpu_count : UINT_MAX;
++
++        result = true;
++
++    done:
++        if (file)
++            fclose(file);
++        PAL_free(filename);
++        PAL_free(line);
++        PAL_free(max_quota_string);
++        PAL_free(period_string);
++
++        return result;
++    }
++
++    static bool GetCGroup1CpuLimit(UINT *val)
++    {
+         long long quota;
+         long long period;
+         double cpu_count;
+ 
+-        quota = ReadCpuCGroupValue(CFS_QUOTA_FILENAME);
++        quota = ReadCpuCGroupValue(CGROUP1_CFS_QUOTA_FILENAME);
+         if (quota <= 0)
+             return false;
+ 
+-        period = ReadCpuCGroupValue(CFS_PERIOD_FILENAME);
++        period = ReadCpuCGroupValue(CGROUP1_CFS_PERIOD_FILENAME);
+         if (period <= 0)
+             return false;
+ 
+@@ -114,27 +256,19 @@
+         return true;
+     }
+ 
+-private:
+-    static bool IsMemorySubsystem(const char *strTok){
+-        return strcmp("memory", strTok) == 0;
+-    }
+-
+-    static bool IsCpuSubsystem(const char *strTok){
+-        return strcmp("cpu", strTok) == 0;
+-    }
+-
+-    static char* FindCgroupPath(bool (*is_subsystem)(const char *)){
++    static char* FindCGroupPath(bool (*is_subsystem)(const char *)){
+         char *cgroup_path = nullptr;
+         char *hierarchy_mount = nullptr;
+         char *hierarchy_root = nullptr;
+         char *cgroup_path_relative_to_mount = nullptr;
++        bool is_cgroupv2 = false;
+         size_t len;
+ 
+-        FindHierarchyMount(is_subsystem, &hierarchy_mount, &hierarchy_root);
++        FindHierarchyMount(is_subsystem, &is_cgroupv2, &hierarchy_mount, &hierarchy_root);
+         if (hierarchy_mount == nullptr || hierarchy_root == nullptr)
+             goto done;
+ 
+-        cgroup_path_relative_to_mount = FindCGroupPathForSubsystem(is_subsystem);
++        cgroup_path_relative_to_mount = FindCGroupPathForSubsystem(is_cgroupv2, is_subsystem);
+         if (cgroup_path_relative_to_mount == nullptr)
+             goto done;
+ 
+@@ -157,7 +291,7 @@
+         return cgroup_path;
+     }
+ 
+-    static void FindHierarchyMount(bool (*is_subsystem)(const char *), char** pmountpath, char** pmountroot)
++    static void FindHierarchyMount(bool (*is_subsystem)(const char *), bool* is_cgroupv2, char** pmountpath, char** pmountroot)
+     {
+         char *line = nullptr;
+         size_t lineLen = 0, maxLineLen = 0;
+@@ -199,11 +333,15 @@
+ 
+             if (strncmp(filesystemType, "cgroup", 6) == 0)
+             {
++                if (strncmp(filesystemType, "cgroup2", 8) == 0)
++                {
++                    *is_cgroupv2 = true;
++                }
+                 char* context = nullptr;
+                 char* strTok = strtok_s(options, ",", &context); 
+                 while (strTok != nullptr)
+                 {
+-                    if (is_subsystem(strTok))
++                    if (*is_cgroupv2 || is_subsystem(strTok))
+                     {
+                         mountpath = (char*)PAL_malloc(lineLen+1);
+                         if (mountpath == nullptr)
+@@ -239,7 +377,7 @@
+             fclose(mountinfofile);
+     }
+ 
+-    static char* FindCGroupPathForSubsystem(bool (*is_subsystem)(const char *))
++    static char* FindCGroupPathForSubsystem(bool is_cgroupv2, bool (*is_subsystem)(const char *))
+     {
+         char *line = nullptr;
+         size_t lineLen = 0;
+@@ -267,27 +405,42 @@
+                 maxLineLen = lineLen;
+             }
+ 
+-            // See man page of proc to get format for /proc/self/cgroup file
+-            int sscanfRet = sscanf_s(line, 
+-                                     "%*[^:]:%[^:]:%s",
+-                                     subsystem_list, lineLen+1,
+-                                     cgroup_path, lineLen+1);
+-            if (sscanfRet != 2)
++            if (is_cgroupv2)
+             {
+-                _ASSERTE(!"Failed to parse cgroup info file contents with sscanf_s.");
+-                goto done;
++                // See https://www.kernel.org/doc/Documentation/cgroup-v2.txt
++                // Look for "0::/some/path"
++                int sscanfRet = sscanf_s(line,
++                                         "0::%s",
++                                         cgroup_path, lineLen+1);
++                if (sscanfRet == 1)
++                {
++                    result = true;
++                }
+             }
+-
+-            char* context = nullptr;
+-            char* strTok = strtok_s(subsystem_list, ",", &context); 
+-            while (strTok != nullptr)
++            else
+             {
+-                if (is_subsystem(strTok))
++                // See man page of proc to get format for /proc/self/cgroup file
++                int sscanfRet = sscanf_s(line,
++                                        "%*[^:]:%[^:]:%s",
++                                        subsystem_list, lineLen+1,
++                                        cgroup_path, lineLen+1);
++                if (sscanfRet != 2)
+                 {
+-                    result = true;
+-                    break;  
++                    _ASSERTE(!"Failed to parse cgroup info file contents with sscanf_s.");
++                    goto done;
++                }
++
++                char* context = nullptr;
++                char* strTok = strtok_s(subsystem_list, ",", &context);
++                while (strTok != nullptr)
++                {
++                    if (is_subsystem(strTok))
++                    {
++                        result = true;
++                        break;
++                    }
++                    strTok = strtok_s(nullptr, ",", &context);
+                 }
+-                strTok = strtok_s(nullptr, ",", &context);
+             }
+         }
+     done:
+@@ -303,6 +456,36 @@
+         return cgroup_path;
+     }
+ 
++    static char* SearchForFile(char* search_root, const char* possible_files[], size_t possible_files_len)
++    {
++        if (search_root == nullptr)
++        {
++            return nullptr;
++        }
++
++        for (size_t i = 0; i < possible_files_len; i++)
++        {
++            const char* possible_file = possible_files[i];
++            size_t len = strlen(search_root);
++            len += strlen(possible_file);
++            char* full_filename = (char*)PAL_malloc(len+1);
++            if (full_filename == nullptr)
++            {
++                return nullptr;
++            }
++            strcpy_s(full_filename, len+1, search_root);
++            strcat_s(full_filename, len+1, possible_file);
++            if (access(full_filename, R_OK) != -1)
++            {
++                return full_filename;
++            }
++
++            PAL_free(full_filename);
++        }
++
++        return nullptr;
++    }
++
+     static bool ReadMemoryValueFromFile(const char* filename, size_t* val)
+     {
+         return ::ReadMemoryValueFromFile(filename, val);
+-- 
+2.23.0
diff --git a/corefx-39633-cgroupv2-mountpoints.patch b/corefx-39633-cgroupv2-mountpoints.patch
new file mode 100644
index 0000000..34fbecb
--- /dev/null
+++ b/corefx-39633-cgroupv2-mountpoints.patch
@@ -0,0 +1,46 @@
+From 1864630f762160e1cb439362cc0577471624192a Mon Sep 17 00:00:00 2001
+From: Omair Majid <omajid@redhat.com>
+Date: Fri, 19 Jul 2019 19:18:51 -0400
+Subject: [PATCH] Fix up cgroup2fs in Interop.MountPoints.FormatInfo
+
+`stat -fc %T /sys/fs/cgroup` calls this file system `cgroup2fs`
+
+Add the cgroup2fs file system magic number. Available from:
+
+  - https://www.kernel.org/doc/Documentation/cgroup-v2.txt
+  - man 2 statfs
+
+Move cgroup2fs next to cgroupfs in the drive type list, since it is also
+DriveType.Ram.
+---
+ .../Unix/System.Native/Interop.MountPoints.FormatInfo.cs       | 3 ++-
+ 1 file changed, 2 insertions(+), 1 deletion(-)
+
+diff --git a/src/Common/src/Interop/Unix/System.Native/Interop.MountPoints.FormatInfo.cs b/src/Common/src/Interop/Unix/System.Native/Interop.MountPoints.FormatInfo.cs
+index af38a2285ba2..4240bd4853ab 100644
+--- a/src/Common/src/Interop/Unix/System.Native/Interop.MountPoints.FormatInfo.cs
++++ b/src/Common/src/Interop/Unix/System.Native/Interop.MountPoints.FormatInfo.cs
+@@ -47,6 +47,7 @@ internal enum UnixFileSystemTypes : long
+             btrfs = 0x9123683E,
+             ceph = 0x00C36400,
+             cgroupfs = 0x0027E0EB,
++            cgroup2fs = 0x63677270,
+             cifs = 0xFF534D42,
+             coda = 0x73757245,
+             coherent = 0x012FF7B7,
+@@ -231,7 +232,6 @@ private static DriveType GetDriveType(string fileSystemName)
+                 case "bpf_fs":
+                 case "btrfs":
+                 case "btrfs_test":
+-                case "cgroup2fs":
+                 case "coh":
+                 case "daxfs":
+                 case "drvfs":
+@@ -384,6 +384,7 @@ private static DriveType GetDriveType(string fileSystemName)
+                 case "binfmt_misc":
+                 case "cgroup":
+                 case "cgroupfs":
++                case "cgroup2fs":
+                 case "configfs":
+                 case "cramfs":
+                 case "cramfs-wend":
diff --git a/corefx-39686-cgroupv2-01.patch b/corefx-39686-cgroupv2-01.patch
new file mode 100644
index 0000000..e7628e2
--- /dev/null
+++ b/corefx-39686-cgroupv2-01.patch
@@ -0,0 +1,391 @@
+From 2b2273ea4ea1c28472fa0d6ad2ffeb6374500550 Mon Sep 17 00:00:00 2001
+From: Omair Majid <omajid@redhat.com>
+Date: Wed, 23 Oct 2019 17:45:59 -0400
+Subject: [PATCH 1/2] Add cgroup v2 support to Interop.cgroups
+
+Fix up code to adjust cgroup v1 assumptions and check cgroup v2 paths,
+locations and values.
+
+Continue using the older cgroup v1 terminology for APIs.
+---
+ .../Interop/Linux/cgroups/Interop.cgroups.cs  | 116 ++++++++++++++----
+ src/Common/tests/Common.Tests.csproj          |   4 +
+ .../tests/Tests/Interop/cgroupsTests.cs       | 107 ++++++++++++++++
+ .../tests/DescriptionNameTests.cs             |   2 +-
+ 4 files changed, 206 insertions(+), 23 deletions(-)
+ create mode 100644 src/Common/tests/Tests/Interop/cgroupsTests.cs
+
+diff --git a/src/Common/src/Interop/Linux/cgroups/Interop.cgroups.cs b/src/Common/src/Interop/Linux/cgroups/Interop.cgroups.cs
+index 0ffd4d7b7c03..186fe0516c5b 100644
+--- a/src/Common/src/Interop/Linux/cgroups/Interop.cgroups.cs
++++ b/src/Common/src/Interop/Linux/cgroups/Interop.cgroups.cs
+@@ -9,17 +9,22 @@
+ 
+ internal static partial class Interop
+ {
++    /// <summary>Provides access to some cgroup (v1 and v2) features</summary>
+     internal static partial class cgroups
+     {
++        // For cgroup v1, see https://www.kernel.org/doc/Documentation/cgroup-v1/
++        // For cgroup v2, see https://www.kernel.org/doc/Documentation/cgroup-v2.txt
++
++        /// <summary>The version of cgroup that's being used </summary>
++        internal enum CGroupVersion { None, CGroup1, CGroup2 };
++
+         /// <summary>Path to mountinfo file in procfs for the current process.</summary>
+         private const string ProcMountInfoFilePath = "/proc/self/mountinfo";
+         /// <summary>Path to cgroup directory in procfs for the current process.</summary>
+         private const string ProcCGroupFilePath = "/proc/self/cgroup";
+ 
+-        /// <summary>Path to the found cgroup location, or null if it couldn't be found.</summary>
+-        internal static readonly string s_cgroupMemoryPath = FindCGroupPath("memory");
+-        /// <summary>Path to the found cgroup memory limit_in_bytes path, or null if it couldn't be found.</summary>
+-        private static readonly string s_cgroupMemoryLimitPath = s_cgroupMemoryPath != null ? s_cgroupMemoryPath + "/memory.limit_in_bytes" : null;
++        /// <summary>Path to the found cgroup memory limit path, or null if it couldn't be found.</summary>
++        internal static readonly string s_cgroupMemoryLimitPath = FindCGroupMemoryLimitPath();
+ 
+         /// <summary>Tries to read the memory limit from the cgroup memory location.</summary>
+         /// <param name="limit">The read limit, or 0 if it couldn't be read.</param>
+@@ -42,7 +47,7 @@ public static bool TryGetMemoryLimit(out ulong limit)
+         /// <param name="path">The path to the file to parse.</param>
+         /// <param name="result">The parsed result, or 0 if it couldn't be parsed.</param>
+         /// <returns>true if the value was read successfully; otherwise, false.</returns>
+-        private static bool TryReadMemoryValueFromFile(string path, out ulong result)
++        internal static bool TryReadMemoryValueFromFile(string path, out ulong result)
+         {
+             if (File.Exists(path))
+             {
+@@ -79,6 +84,11 @@ private static bool TryReadMemoryValueFromFile(string path, out ulong result)
+                         result = checked(ulongValue * multiplier);
+                         return true;
+                     }
++
++                    // 'max' is also a possible valid value
++                    //
++                    // Treat this as 'no memory limit' and let the caller
++                    // fallback to reading the real limit via other means
+                 }
+                 catch (Exception e)
+                 {
+@@ -90,12 +100,35 @@ private static bool TryReadMemoryValueFromFile(string path, out ulong result)
+             return false;
+         }
+ 
++        /// <summary>Find the cgroup memory limit path.</summary>
++        /// <returns>The limit path if found; otherwise, null.</returns>
++        private static string FindCGroupMemoryLimitPath()
++        {
++            string cgroupMemoryPath = FindCGroupPath("memory", out CGroupVersion version);
++            if (cgroupMemoryPath != null)
++            {
++                if (version == CGroupVersion.CGroup1)
++                {
++                    return cgroupMemoryPath + "/memory.limit_in_bytes";
++                }
++
++                if (version == CGroupVersion.CGroup2)
++                {
++                    // 'memory.high' is a soft limit; the process may get throttled
++                    // 'memory.max' is where OOM killer kicks in
++                    return cgroupMemoryPath + "/memory.max";
++                }
++            }
++
++            return null;
++        }
++
+         /// <summary>Find the cgroup path for the specified subsystem.</summary>
+         /// <param name="subsystem">The subsystem, e.g. "memory".</param>
+         /// <returns>The cgroup path if found; otherwise, null.</returns>
+-        private static string FindCGroupPath(string subsystem)
++        private static string FindCGroupPath(string subsystem, out CGroupVersion version)
+         {
+-            if (TryFindHierarchyMount(subsystem, out string hierarchyRoot, out string hierarchyMount) &&
++            if (TryFindHierarchyMount(subsystem, out version, out string hierarchyRoot, out string hierarchyMount) &&
+                 TryFindCGroupPathForSubsystem(subsystem, out string cgroupPathRelativeToMount))
+             {
+                 // For a host cgroup, we need to append the relative path.
+@@ -113,19 +146,24 @@ private static string FindCGroupPath(string subsystem)
+         /// <param name="root">The path of the directory in the filesystem which forms the root of this mount; null if not found.</param>
+         /// <param name="path">The path of the mount point relative to the process's root directory; null if not found.</param>
+         /// <returns>true if the mount was found; otherwise, null.</returns>
+-        private static bool TryFindHierarchyMount(string subsystem, out string root, out string path)
++        private static bool TryFindHierarchyMount(string subsystem, out CGroupVersion version, out string root, out string path)
+         {
+-            if (File.Exists(ProcMountInfoFilePath))
++            return TryFindHierarchyMount(ProcMountInfoFilePath, subsystem, out version, out root, out path);
++        }
++
++        internal static bool TryFindHierarchyMount(string mountInfoFilePath, string subsystem, out CGroupVersion version, out string root, out string path)
++        {
++            if (File.Exists(mountInfoFilePath))
+             {
+                 try
+                 {
+-                    using (var reader = new StreamReader(ProcMountInfoFilePath))
++                    using (var reader = new StreamReader(mountInfoFilePath))
+                     {
+                         string line;
+                         while ((line = reader.ReadLine()) != null)
+                         {
+                             // Look for an entry that has cgroup as the "filesystem type"
+-                            // and that has options containing the specified subsystem.
++                            // and, for cgroup1, that has options containing the specified subsystem
+                             // See man page for /proc/[pid]/mountinfo for details, e.g.:
+                             //     (1)(2)(3)   (4)   (5)      (6)      (7)   (8) (9)   (10)         (11)
+                             //     36 35 98:0 /mnt1 /mnt2 rw,noatime master:1 - ext3 /dev/root rw,errors=continue
+@@ -148,17 +186,35 @@ private static bool TryFindHierarchyMount(string subsystem, out string root, out
+                                 continue;
+                             }
+ 
+-                            if (postSeparatorlineParts[0] != "cgroup" ||
+-                                Array.IndexOf(postSeparatorlineParts[2].Split(','), subsystem) < 0)
++                            bool validCGroup1Entry = ((postSeparatorlineParts[0] == "cgroup") &&
++                                    (Array.IndexOf(postSeparatorlineParts[2].Split(','), subsystem) >= 0));
++                            bool validCGroup2Entry = postSeparatorlineParts[0] == "cgroup2";
++
++                            if (!validCGroup1Entry && !validCGroup2Entry)
+                             {
+                                 // Not the relevant entry.
+                                 continue;
+                             }
+ 
+-                            // Found the relevant entry.  Extract the mount root and path.
++                            // Found the relevant entry.  Extract the cgroup version, mount root and path.
++                            switch (postSeparatorlineParts[0])
++                            {
++                                case "cgroup":
++                                    version = CGroupVersion.CGroup1;
++                                    break;
++                                case "cgroup2":
++                                    version = CGroupVersion.CGroup2;
++                                    break;
++                                default:
++                                    version = CGroupVersion.None;
++                                    Debug.Fail($"invalid value for CGroupVersion \"{postSeparatorlineParts[0]}\"");
++                                    break;
++                            }
++
+                             string[] lineParts = line.Substring(0, endOfOptionalFields).Split(' ');
+                             root = lineParts[3];
+                             path = lineParts[4];
++
+                             return true;
+                         }
+                     }
+@@ -169,6 +225,7 @@ private static bool TryFindHierarchyMount(string subsystem, out string root, out
+                 }
+             }
+ 
++            version = CGroupVersion.None;
+             root = null;
+             path = null;
+             return false;
+@@ -180,27 +237,42 @@ private static bool TryFindHierarchyMount(string subsystem, out string root, out
+         /// <returns></returns>
+         private static bool TryFindCGroupPathForSubsystem(string subsystem, out string path)
+         {
+-            if (File.Exists(ProcCGroupFilePath))
++            return TryFindCGroupPathForSubsystem(ProcCGroupFilePath, subsystem, out path);
++        }
++
++        internal static bool TryFindCGroupPathForSubsystem(string procCGroupFilePath, string subsystem, out string path)
++        {
++            if (File.Exists(procCGroupFilePath))
+             {
+                 try
+                 {
+-                    using (var reader = new StreamReader(ProcCGroupFilePath))
++                    using (var reader = new StreamReader(procCGroupFilePath))
+                     {
+                         string line;
+                         while ((line = reader.ReadLine()) != null)
+                         {
+-                            // Find the first entry that has the subsystem listed in its controller
+-                            // list. See man page for cgroups for /proc/[pid]/cgroups format, e.g:
+-                            //     hierarchy-ID:controller-list:cgroup-path
+-                            //     5:cpuacct,cpu,cpuset:/daemons
+-
+                             string[] lineParts = line.Split(':');
++
+                             if (lineParts.Length != 3)
+                             {
+                                 // Malformed line.
+                                 continue;
+                             }
+ 
++                            // cgroup v2: Find the first entry that matches the cgroup v2 hierarchy:
++                            //     0::$PATH
++
++                            if ((lineParts[0] == "0") && (string.Empty == lineParts[1]))
++                            {
++                                path = lineParts[2];
++                                return true;
++                            }
++
++                            // cgroup v1: Find the first entry that has the subsystem listed in its controller
++                            // list. See man page for cgroups for /proc/[pid]/cgroups format, e.g:
++                            //     hierarchy-ID:controller-list:cgroup-path
++                            //     5:cpuacct,cpu,cpuset:/daemons
++
+                             if (Array.IndexOf(lineParts[1].Split(','), subsystem) < 0)
+                             {
+                                 // Not the relevant entry.
+@@ -214,7 +286,7 @@ private static bool TryFindCGroupPathForSubsystem(string subsystem, out string p
+                 }
+                 catch (Exception e)
+                 {
+-                    Debug.Fail($"Failed to read or parse \"{ProcMountInfoFilePath}\": {e}");
++                    Debug.Fail($"Failed to read or parse \"{procCGroupFilePath}\": {e}");
+                 }
+             }
+ 
+diff --git a/src/Common/tests/Common.Tests.csproj b/src/Common/tests/Common.Tests.csproj
+index a189d856348b..979c8dd7fbe6 100644
+--- a/src/Common/tests/Common.Tests.csproj
++++ b/src/Common/tests/Common.Tests.csproj
+@@ -12,6 +12,9 @@
+     <Compile Include="$(CommonTestPath)\System\Security\Cryptography\ByteUtils.cs">
+       <Link>Common\System\Security\Cryptography\ByteUtils.cs</Link>
+     </Compile>
++    <Compile Include="$(CommonPath)\Interop\Linux\cgroups\Interop.cgroups.cs">
++      <Link>Common\Interop\Linux\cgroups\Interop.cgroups.cs</Link>
++    </Compile>
+     <Compile Include="$(CommonPath)\Interop\Linux\procfs\Interop.ProcFsStat.cs">
+       <Link>Common\Interop\Linux\procfs\Interop.ProcFsStat.cs</Link>
+     </Compile>
+@@ -69,6 +72,7 @@
+     <Compile Include="$(CommonPath)\CoreLib\System\PasteArguments.cs">
+       <Link>Common\CoreLib\System\PasteArguments.cs</Link>
+     </Compile>
++    <Compile Include="Tests\Interop\cgroupsTests.cs" />
+     <Compile Include="Tests\Interop\procfsTests.cs" />
+     <Compile Include="Tests\System\CharArrayHelpersTests.cs" />
+     <Compile Include="Tests\System\IO\PathInternal.Tests.cs" />
+diff --git a/src/Common/tests/Tests/Interop/cgroupsTests.cs b/src/Common/tests/Tests/Interop/cgroupsTests.cs
+new file mode 100644
+index 000000000000..f16d9242879c
+--- /dev/null
++++ b/src/Common/tests/Tests/Interop/cgroupsTests.cs
+@@ -0,0 +1,107 @@
++﻿// Licensed to the .NET Foundation under one or more agreements.
++// The .NET Foundation licenses this file to you under the MIT license.
++// See the LICENSE file in the project root for more information.
++
++using System;
++using System.IO;
++using System.Text;
++using Xunit;
++
++namespace Common.Tests
++{
++    public class cgroupsTests
++    {
++        [Theory]
++        [InlineData(true, "0",  0)]
++        [InlineData(false, "max",  0)]
++        [InlineData(true, "1k",  1024)]
++        [InlineData(true, "1K",  1024)]
++        public static void ValidateTryReadMemoryValue(bool expectedResult, string valueText, ulong expectedValue)
++        {
++            string path = Path.GetTempFileName();
++            try
++            {
++                File.WriteAllText(path, valueText);
++
++                bool result = Interop.cgroups.TryReadMemoryValueFromFile(path, out ulong val);
++
++                Assert.Equal(expectedResult, result);
++                if (result)
++                {
++                    Assert.Equal(expectedValue, val);
++                }
++            }
++            finally
++            {
++                File.Delete(path);
++            }
++        }
++
++        [Theory]
++        [InlineData(false, "0 0 0:0 / /foo ignore ignore - overlay overlay ignore", "ignore", 0, "/", "/")]
++        [InlineData(true, "0 0 0:0 / /foo ignore ignore - cgroup2 cgroup2 ignore", "ignore", 2, "/", "/foo")]
++        [InlineData(true, "0 0 0:0 / /foo ignore ignore - cgroup2 cgroup2 ignore", "memory", 2, "/", "/foo")]
++        [InlineData(true, "0 0 0:0 / /foo ignore ignore - cgroup2 cgroup2 ignore", "cpu", 2, "/", "/foo")]
++        [InlineData(true, "0 0 0:0 / /foo ignore - cgroup2 cgroup2 ignore", "cpu", 2, "/", "/foo")]
++        [InlineData(true, "0 0 0:0 / /foo ignore ignore ignore - cgroup2 cgroup2 ignore", "cpu", 2, "/", "/foo")]
++        [InlineData(true, "0 0 0:0 / /foo-with-dashes ignore ignore - cgroup2 cgroup2 ignore", "ignore", 2, "/", "/foo-with-dashes")]
++        [InlineData(true, "0 0 0:0 / /foo ignore ignore - cgroup cgroup memory", "memory", 1, "/", "/foo")]
++        [InlineData(true, "0 0 0:0 / /foo-with-dashes ignore ignore - cgroup cgroup memory", "memory", 1, "/", "/foo-with-dashes")]
++        [InlineData(true, "0 0 0:0 / /foo ignore ignore - cgroup cgroup cpu,memory", "memory", 1, "/", "/foo")]
++        [InlineData(true, "0 0 0:0 / /foo ignore ignore - cgroup cgroup memory,cpu", "memory", 1, "/", "/foo")]
++        [InlineData(false, "0 0 0:0 / /foo ignore ignore - cgroup cgroup cpu", "memory", 0, "/", "/foo")]
++        public static void ParseValidateMountInfo(bool found, string procSelfMountInfoText, string subsystem, int expectedVersion, string expectedRoot, string expectedMount)
++        {
++            string path = Path.GetTempFileName();
++            try
++            {
++                File.WriteAllText(path, procSelfMountInfoText);
++
++                bool result = Interop.cgroups.TryFindHierarchyMount(path, subsystem,  out Interop.cgroups.CGroupVersion version, out string root, out string mount);
++
++                Assert.Equal(found, result);
++                if (found)
++                {
++                    Assert.Equal(expectedVersion, (int)version);
++                    Assert.Equal(expectedRoot, root);
++                    Assert.Equal(expectedMount, mount);
++                }
++            }
++            finally
++            {
++                File.Delete(path);
++            }
++        }
++
++        [Theory]
++        [InlineData(true, "0::/foo", "ignore", "/foo")]
++        [InlineData(true, "0::/bar", "ignore", "/bar")]
++        [InlineData(true, "0::frob", "ignore", "frob")]
++        [InlineData(false, "1::frob", "ignore", "ignore")]
++        [InlineData(true, "1:foo:bar", "foo", "bar")]
++        [InlineData(true, "2:foo:bar", "foo", "bar")]
++        [InlineData(false, "2:foo:bar", "bar", "ignore")]
++        [InlineData(true, "1:foo:bar\n2:eggs:spam", "foo", "bar")]
++        [InlineData(true, "1:foo:bar\n2:eggs:spam", "eggs", "spam")]
++        public static void ParseValidateProcCGroup(bool found, string procSelfCgroupText, string subsystem, string expectedMountPath)
++        {
++            string path = Path.GetTempFileName();
++            try
++            {
++                File.WriteAllText(path, procSelfCgroupText);
++
++                bool result = Interop.cgroups.TryFindCGroupPathForSubsystem(path, subsystem,  out string mountPath);
++
++                Assert.Equal(found, result);
++                if (found)
++                {
++                    Assert.Equal(expectedMountPath, mountPath);
++                }
++            }
++            finally
++            {
++                File.Delete(path);
++            }
++        }
++    }
++}
+diff --git a/src/System.Runtime.InteropServices.RuntimeInformation/tests/DescriptionNameTests.cs b/src/System.Runtime.InteropServices.RuntimeInformation/tests/DescriptionNameTests.cs
+index 910af2fd82b4..73f692898dbc 100644
+--- a/src/System.Runtime.InteropServices.RuntimeInformation/tests/DescriptionNameTests.cs
++++ b/src/System.Runtime.InteropServices.RuntimeInformation/tests/DescriptionNameTests.cs
+@@ -40,7 +40,7 @@ public void DumpRuntimeInformationToConsole()
+ 
+             Console.WriteLine($"### CURRENT DIRECTORY: {Environment.CurrentDirectory}");
+ 
+-            string cgroupsLocation = Interop.cgroups.s_cgroupMemoryPath;
++            string cgroupsLocation = Interop.cgroups.s_cgroupMemoryLimitPath;
+             if (cgroupsLocation != null)
+             {
+                 Console.WriteLine($"### CGROUPS MEMORY: {cgroupsLocation}");
+
diff --git a/corefx-39686-cgroupv2-02.patch b/corefx-39686-cgroupv2-02.patch
new file mode 100644
index 0000000..88dcd99
--- /dev/null
+++ b/corefx-39686-cgroupv2-02.patch
@@ -0,0 +1,129 @@
+From 9a8c5e4014ffca8aff70808cc0e50a403d38c292 Mon Sep 17 00:00:00 2001
+From: Stephen Toub <stoub@microsoft.com>
+Date: Wed, 23 Oct 2019 20:35:49 -0400
+Subject: [PATCH 2/2] Clean up new tests
+
+---
+ .../tests/Tests/Interop/cgroupsTests.cs       | 79 ++++++-------------
+ 1 file changed, 25 insertions(+), 54 deletions(-)
+
+diff --git a/src/Common/tests/Tests/Interop/cgroupsTests.cs b/src/Common/tests/Tests/Interop/cgroupsTests.cs
+index f16d9242879c..fc6ab5c9753c 100644
+--- a/src/Common/tests/Tests/Interop/cgroupsTests.cs
++++ b/src/Common/tests/Tests/Interop/cgroupsTests.cs
+@@ -2,38 +2,27 @@
+ // The .NET Foundation licenses this file to you under the MIT license.
+ // See the LICENSE file in the project root for more information.
+ 
+-using System;
+ using System.IO;
+-using System.Text;
+ using Xunit;
+ 
+ namespace Common.Tests
+ {
+-    public class cgroupsTests
++    public class cgroupsTests : FileCleanupTestBase
+     {
+         [Theory]
+-        [InlineData(true, "0",  0)]
+-        [InlineData(false, "max",  0)]
+-        [InlineData(true, "1k",  1024)]
+-        [InlineData(true, "1K",  1024)]
+-        public static void ValidateTryReadMemoryValue(bool expectedResult, string valueText, ulong expectedValue)
++        [InlineData(true, "0", 0)]
++        [InlineData(false, "max", 0)]
++        [InlineData(true, "1k", 1024)]
++        [InlineData(true, "1K", 1024)]
++        public void ValidateTryReadMemoryValue(bool expectedResult, string valueText, ulong expectedValue)
+         {
+-            string path = Path.GetTempFileName();
+-            try
+-            {
+-                File.WriteAllText(path, valueText);
+-
+-                bool result = Interop.cgroups.TryReadMemoryValueFromFile(path, out ulong val);
++            string path = GetTestFilePath();
++            File.WriteAllText(path, valueText);
+ 
+-                Assert.Equal(expectedResult, result);
+-                if (result)
+-                {
+-                    Assert.Equal(expectedValue, val);
+-                }
+-            }
+-            finally
++            Assert.Equal(expectedResult, Interop.cgroups.TryReadMemoryValueFromFile(path, out ulong val));
++            if (expectedResult)
+             {
+-                File.Delete(path);
++                Assert.Equal(expectedValue, val);
+             }
+         }
+ 
+@@ -50,26 +39,17 @@ public static void ValidateTryReadMemoryValue(bool expectedResult, string valueT
+         [InlineData(true, "0 0 0:0 / /foo ignore ignore - cgroup cgroup cpu,memory", "memory", 1, "/", "/foo")]
+         [InlineData(true, "0 0 0:0 / /foo ignore ignore - cgroup cgroup memory,cpu", "memory", 1, "/", "/foo")]
+         [InlineData(false, "0 0 0:0 / /foo ignore ignore - cgroup cgroup cpu", "memory", 0, "/", "/foo")]
+-        public static void ParseValidateMountInfo(bool found, string procSelfMountInfoText, string subsystem, int expectedVersion, string expectedRoot, string expectedMount)
++        public void ParseValidateMountInfo(bool expectedFound, string procSelfMountInfoText, string subsystem, int expectedVersion, string expectedRoot, string expectedMount)
+         {
+-            string path = Path.GetTempFileName();
+-            try
+-            {
+-                File.WriteAllText(path, procSelfMountInfoText);
+-
+-                bool result = Interop.cgroups.TryFindHierarchyMount(path, subsystem,  out Interop.cgroups.CGroupVersion version, out string root, out string mount);
++            string path = GetTestFilePath();
++            File.WriteAllText(path, procSelfMountInfoText);
+ 
+-                Assert.Equal(found, result);
+-                if (found)
+-                {
+-                    Assert.Equal(expectedVersion, (int)version);
+-                    Assert.Equal(expectedRoot, root);
+-                    Assert.Equal(expectedMount, mount);
+-                }
+-            }
+-            finally
++            Assert.Equal(expectedFound, Interop.cgroups.TryFindHierarchyMount(path, subsystem, out Interop.cgroups.CGroupVersion version, out string root, out string mount));
++            if (expectedFound)
+             {
+-                File.Delete(path);
++                Assert.Equal(expectedVersion, (int)version);
++                Assert.Equal(expectedRoot, root);
++                Assert.Equal(expectedMount, mount);
+             }
+         }
+ 
+@@ -83,24 +63,15 @@ public static void ParseValidateMountInfo(bool found, string procSelfMountInfoTe
+         [InlineData(false, "2:foo:bar", "bar", "ignore")]
+         [InlineData(true, "1:foo:bar\n2:eggs:spam", "foo", "bar")]
+         [InlineData(true, "1:foo:bar\n2:eggs:spam", "eggs", "spam")]
+-        public static void ParseValidateProcCGroup(bool found, string procSelfCgroupText, string subsystem, string expectedMountPath)
++        public void ParseValidateProcCGroup(bool expectedFound, string procSelfCgroupText, string subsystem, string expectedMountPath)
+         {
+-            string path = Path.GetTempFileName();
+-            try
+-            {
+-                File.WriteAllText(path, procSelfCgroupText);
++            string path = GetTestFilePath();
++            File.WriteAllText(path, procSelfCgroupText);
+ 
+-                bool result = Interop.cgroups.TryFindCGroupPathForSubsystem(path, subsystem,  out string mountPath);
+-
+-                Assert.Equal(found, result);
+-                if (found)
+-                {
+-                    Assert.Equal(expectedMountPath, mountPath);
+-                }
+-            }
+-            finally
++            Assert.Equal(expectedFound, Interop.cgroups.TryFindCGroupPathForSubsystem(path, subsystem, out string mountPath));
++            if (expectedFound)
+             {
+-                File.Delete(path);
++                Assert.Equal(expectedMountPath, mountPath);
+             }
+         }
+     }
diff --git a/dotnet3.0.spec b/dotnet3.0.spec
index 6f5cc10..623479a 100644
--- a/dotnet3.0.spec
+++ b/dotnet3.0.spec
@@ -52,7 +52,7 @@
 
 Name:           dotnet3.0
 Version:        %{sdk_rpm_version}
-Release:        4%{?dist}
+Release:        5%{?dist}
 Summary:        .NET Core CLI tools and runtime
 License:        MIT and ASL 2.0 and BSD
 URL:            https://github.com/dotnet/
@@ -64,9 +64,14 @@ Source1:        check-debug-symbols.py
 Source2:        dotnet.sh.in
 
 Patch100:       corefx-optflags-support.patch
+Patch101:       corefx-39686-cgroupv2-01.patch
+Patch102:       corefx-39686-cgroupv2-02.patch
+Patch103:       corefx-39633-cgroupv2-mountpoints.patch
 
 Patch200:       coreclr-27048-sysctl-deprecation.patch
 Patch201:       coreclr-hardening-flags.patch
+Patch202:       coreclr-cgroupv2.patch
+Patch203:       coreclr-cgroupv2-02.patch
 
 Patch300:       core-setup-do-not-strip.patch
 Patch301:       core-setup-hardening-flags.patch
@@ -94,13 +99,11 @@ BuildRequires:  libicu-devel
 %if ! %{use_bundled_libunwind}
 BuildRequires:  libunwind-devel
 %endif
-BuildRequires:  libuuid-devel
 BuildRequires:  lldb-devel
 BuildRequires:  llvm
 BuildRequires:  lttng-ust-devel
 BuildRequires:  openssl-devel
 BuildRequires:  python3
-BuildRequires:  strace
 BuildRequires:  tar
 BuildRequires:  zlib-devel
 
@@ -302,11 +305,16 @@ sed -i 's|skiptests|skiptests ignorewarnings|' repos/coreclr.proj
 
 pushd src/corefx.*
 %patch100 -p1
+%patch101 -p1
+%patch102 -p1
+%patch103 -p1
 popd
 
 pushd src/coreclr.*
 %patch200 -p1
 %patch201 -p1
+%patch202 -p1
+%patch203 -p1
 popd
 
 pushd src/core-setup.*
@@ -438,6 +446,9 @@ echo "Testing build results for debug symbols..."
 %dir %{_libdir}/dotnet/packs
 
 %changelog
+* Thu Oct 24 2019 Omair Majid <omajid@redhat.com> - 3.0.100-5
+- Add cgroupv2 support to .NET Core
+
 * Wed Oct 16 2019 Omair Majid <omajid@redhat.com> - 3.0.100-4
 - Include fix from coreclr for building on Fedora 32
 

