From c1c14b5e7c98d9537253e290cb6cb111a210d044 Mon Sep 17 00:00:00 2001 From: Adi Benziony Date: Sun, 16 Jan 2022 13:31:53 +0200 Subject: [PATCH 01/19] Copy get_process_nspid() from gprofiler --- granulate_utils/linux/ns.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/granulate_utils/linux/ns.py b/granulate_utils/linux/ns.py index 4fc76828..7edef120 100644 --- a/granulate_utils/linux/ns.py +++ b/granulate_utils/linux/ns.py @@ -67,6 +67,20 @@ def resolve_proc_root_links(proc_root: str, ns_path: str) -> str: return path +def get_process_nspid(pid: int) -> Optional[int]: + with open(f"/proc/{pid}/status") as f: + for line in f: + fields = line.split() + if fields[0] == "NSpid:": + return int(fields[-1]) + + # old kernel (pre 4.1) with no NSpid. + # TODO if needed, this can be implemented for pre 4.1, by reading all /proc/pid/sched files as + # seen by the PID NS; they expose the init NS PID (due to a bug fixed in 4.14~), and we can get the NS PID + # from the listing of those files itself. + return None + + def is_same_ns(pid: int, nstype: str, pid2: int = None) -> bool: return ( os.stat(f"/proc/{pid2 if pid2 is not None else 'self'}/ns/{nstype}").st_ino From de305dfc5da475363f992806010b8421d65cfeb2 Mon Sep 17 00:00:00 2001 From: Adi Benziony Date: Sun, 16 Jan 2022 13:47:10 +0200 Subject: [PATCH 02/19] Make get_process_nspid() support older kernels --- granulate_utils/linux/ns.py | 44 ++++++++++++++++++++++++++++++++----- 1 file changed, 39 insertions(+), 5 deletions(-) diff --git a/granulate_utils/linux/ns.py b/granulate_utils/linux/ns.py index 7edef120..202a88fa 100644 --- a/granulate_utils/linux/ns.py +++ b/granulate_utils/linux/ns.py @@ -6,6 +6,7 @@ import ctypes import enum import os +import re from pathlib import Path from threading import Thread from typing import Callable, List, Optional, TypeVar, Union @@ -74,11 +75,44 @@ def get_process_nspid(pid: int) -> Optional[int]: if fields[0] == "NSpid:": return int(fields[-1]) - # old kernel (pre 4.1) with no NSpid. - # TODO if needed, this can be implemented for pre 4.1, by reading all /proc/pid/sched files as - # seen by the PID NS; they expose the init NS PID (due to a bug fixed in 4.14~), and we can get the NS PID - # from the listing of those files itself. - return None + if is_same_ns(pid, NsType.pid.name): + # If we're in the same PID namespace, then the outer PID is also the inner pid (NSpid) + return pid + + return _get_process_nspid_by_sched_files(pid) + + +def _get_process_nspid_by_sched_files(pid: int): + # Old kernel (pre 4.1) doesn't have an NSpid field in their /proc/pid/status file + # Instead, we can look through all /proc/*/sched files from inside the process' pid namespace, and due to a bug + # (fixed in 4.14) the outer PID is exposed, so we can find the target process by comparing the outer PID + + def _find_inner_pid() -> Optional[int]: + pattern = re.compile(r"\((\d+), #threads: ") + + procfs = Path('/proc') + for process_dir in procfs.iterdir(): + is_process_dir = process_dir.is_dir() and process_dir.name.isdigit() + if not is_process_dir: + continue + + try: + sched_file = process_dir / 'sched' + sched_contents = sched_file.open('r').readline() + match = pattern.search(sched_contents) + if match: + outer_pid = int(match.group(1)) + if outer_pid == pid: + return int(process_dir.name) + except FileNotFoundError: + # That's OK, processes might disappear before we get the chance to handle them + continue + + return None + + # We're searching `/proc`, so we only need to set our mount namespace + inner_pid = run_in_ns(['mnt'], _find_inner_pid, pid) + return inner_pid def is_same_ns(pid: int, nstype: str, pid2: int = None) -> bool: From fc06fa239433bd965836bf212be54572a8b0b738 Mon Sep 17 00:00:00 2001 From: Adi Benziony Date: Sun, 16 Jan 2022 16:31:05 +0200 Subject: [PATCH 03/19] Reformat --- granulate_utils/linux/ns.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/granulate_utils/linux/ns.py b/granulate_utils/linux/ns.py index 202a88fa..9f30589e 100644 --- a/granulate_utils/linux/ns.py +++ b/granulate_utils/linux/ns.py @@ -90,15 +90,15 @@ def _get_process_nspid_by_sched_files(pid: int): def _find_inner_pid() -> Optional[int]: pattern = re.compile(r"\((\d+), #threads: ") - procfs = Path('/proc') + procfs = Path("/proc") for process_dir in procfs.iterdir(): is_process_dir = process_dir.is_dir() and process_dir.name.isdigit() if not is_process_dir: continue try: - sched_file = process_dir / 'sched' - sched_contents = sched_file.open('r').readline() + sched_file = process_dir / "sched" + sched_contents = sched_file.open("r").readline() match = pattern.search(sched_contents) if match: outer_pid = int(match.group(1)) @@ -111,7 +111,7 @@ def _find_inner_pid() -> Optional[int]: return None # We're searching `/proc`, so we only need to set our mount namespace - inner_pid = run_in_ns(['mnt'], _find_inner_pid, pid) + inner_pid = run_in_ns(["mnt"], _find_inner_pid, pid) return inner_pid From d45a608deb20bbd8fa1e4ae83e7d7dc5255c02a3 Mon Sep 17 00:00:00 2001 From: Adi Benziony Date: Sun, 16 Jan 2022 16:56:30 +0200 Subject: [PATCH 04/19] Use a context manager to read sched's first line --- granulate_utils/linux/ns.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/granulate_utils/linux/ns.py b/granulate_utils/linux/ns.py index 9f30589e..59182bab 100644 --- a/granulate_utils/linux/ns.py +++ b/granulate_utils/linux/ns.py @@ -97,13 +97,15 @@ def _find_inner_pid() -> Optional[int]: continue try: - sched_file = process_dir / "sched" - sched_contents = sched_file.open("r").readline() - match = pattern.search(sched_contents) - if match: - outer_pid = int(match.group(1)) - if outer_pid == pid: - return int(process_dir.name) + sched_file_path = process_dir / "sched" + with sched_file_path.open("r") as sched_file: + sched_contents = sched_file.readline() # The first line contains the outer PID + + match = pattern.search(sched_contents) + if match: + outer_pid = int(match.group(1)) + if outer_pid == pid: + return int(process_dir.name) except FileNotFoundError: # That's OK, processes might disappear before we get the chance to handle them continue From 98500c0bfeb7f14b551853e764e2872ede2b6108 Mon Sep 17 00:00:00 2001 From: Adi Benziony Date: Sun, 16 Jan 2022 16:58:06 +0200 Subject: [PATCH 05/19] Give a match example for the regex --- granulate_utils/linux/ns.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/granulate_utils/linux/ns.py b/granulate_utils/linux/ns.py index 59182bab..61f75e79 100644 --- a/granulate_utils/linux/ns.py +++ b/granulate_utils/linux/ns.py @@ -88,7 +88,7 @@ def _get_process_nspid_by_sched_files(pid: int): # (fixed in 4.14) the outer PID is exposed, so we can find the target process by comparing the outer PID def _find_inner_pid() -> Optional[int]: - pattern = re.compile(r"\((\d+), #threads: ") + pattern = re.compile(r"\((\d+), #threads: ") # Match example: "java (12329, #threads: 11)" procfs = Path("/proc") for process_dir in procfs.iterdir(): From 6d35c0d24d2d955fd32254cb0b7d6bb970e765ca Mon Sep 17 00:00:00 2001 From: Adi Benziony Date: Sun, 16 Jan 2022 16:58:27 +0200 Subject: [PATCH 06/19] Check if match is explicitly not None --- granulate_utils/linux/ns.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/granulate_utils/linux/ns.py b/granulate_utils/linux/ns.py index 61f75e79..d794a830 100644 --- a/granulate_utils/linux/ns.py +++ b/granulate_utils/linux/ns.py @@ -102,7 +102,7 @@ def _find_inner_pid() -> Optional[int]: sched_contents = sched_file.readline() # The first line contains the outer PID match = pattern.search(sched_contents) - if match: + if match is not None: outer_pid = int(match.group(1)) if outer_pid == pid: return int(process_dir.name) From cfc102dc416ab8ce0c1812d22a3f80c6ac09c035 Mon Sep 17 00:00:00 2001 From: Adi Benziony Date: Sun, 16 Jan 2022 17:03:27 +0200 Subject: [PATCH 07/19] Reduce file read exception scope --- granulate_utils/linux/ns.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/granulate_utils/linux/ns.py b/granulate_utils/linux/ns.py index d794a830..ab6e36e7 100644 --- a/granulate_utils/linux/ns.py +++ b/granulate_utils/linux/ns.py @@ -100,16 +100,16 @@ def _find_inner_pid() -> Optional[int]: sched_file_path = process_dir / "sched" with sched_file_path.open("r") as sched_file: sched_contents = sched_file.readline() # The first line contains the outer PID - - match = pattern.search(sched_contents) - if match is not None: - outer_pid = int(match.group(1)) - if outer_pid == pid: - return int(process_dir.name) except FileNotFoundError: # That's OK, processes might disappear before we get the chance to handle them continue + match = pattern.search(sched_contents) + if match is not None: + outer_pid = int(match.group(1)) + if outer_pid == pid: + return int(process_dir.name) + return None # We're searching `/proc`, so we only need to set our mount namespace From bc6149e14960024f6d36aa4f6b5255ea9232edb5 Mon Sep 17 00:00:00 2001 From: Adi Benziony Date: Sun, 16 Jan 2022 17:08:54 +0200 Subject: [PATCH 08/19] Rename procfs var --- granulate_utils/linux/ns.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/granulate_utils/linux/ns.py b/granulate_utils/linux/ns.py index ab6e36e7..92a267e6 100644 --- a/granulate_utils/linux/ns.py +++ b/granulate_utils/linux/ns.py @@ -91,13 +91,13 @@ def _find_inner_pid() -> Optional[int]: pattern = re.compile(r"\((\d+), #threads: ") # Match example: "java (12329, #threads: 11)" procfs = Path("/proc") - for process_dir in procfs.iterdir(): - is_process_dir = process_dir.is_dir() and process_dir.name.isdigit() + for procfs_child in procfs.iterdir(): + is_process_dir = procfs_child.is_dir() and procfs_child.name.isdigit() if not is_process_dir: continue try: - sched_file_path = process_dir / "sched" + sched_file_path = procfs_child / "sched" with sched_file_path.open("r") as sched_file: sched_contents = sched_file.readline() # The first line contains the outer PID except FileNotFoundError: @@ -108,7 +108,7 @@ def _find_inner_pid() -> Optional[int]: if match is not None: outer_pid = int(match.group(1)) if outer_pid == pid: - return int(process_dir.name) + return int(procfs_child.name) return None From 3a0e6bcc30fb04d576fae04ab932d73bf3604322 Mon Sep 17 00:00:00 2001 From: Adi Benziony Date: Sun, 16 Jan 2022 17:18:17 +0200 Subject: [PATCH 09/19] Some refactoring --- granulate_utils/linux/ns.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/granulate_utils/linux/ns.py b/granulate_utils/linux/ns.py index 92a267e6..e39324de 100644 --- a/granulate_utils/linux/ns.py +++ b/granulate_utils/linux/ns.py @@ -99,12 +99,12 @@ def _find_inner_pid() -> Optional[int]: try: sched_file_path = procfs_child / "sched" with sched_file_path.open("r") as sched_file: - sched_contents = sched_file.readline() # The first line contains the outer PID + sched_header_line = sched_file.readline() # The first line contains the outer PID except FileNotFoundError: # That's OK, processes might disappear before we get the chance to handle them continue - match = pattern.search(sched_contents) + match = pattern.search(sched_header_line) if match is not None: outer_pid = int(match.group(1)) if outer_pid == pid: From 25adb3cb89b9075722b9b567c91394095ed480b6 Mon Sep 17 00:00:00 2001 From: Adi Benziony Date: Sun, 16 Jan 2022 17:42:20 +0200 Subject: [PATCH 10/19] Trying to read the sched file might yield ProcessLookupError This happend when the process dies between us opening the sched file and us reading it. Appearently procfs returns ESRCH when trying to read a procfs file of a dead process, and Python converts it to a ProcessLookupError --- granulate_utils/linux/ns.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/granulate_utils/linux/ns.py b/granulate_utils/linux/ns.py index e39324de..721ca4d4 100644 --- a/granulate_utils/linux/ns.py +++ b/granulate_utils/linux/ns.py @@ -100,7 +100,7 @@ def _find_inner_pid() -> Optional[int]: sched_file_path = procfs_child / "sched" with sched_file_path.open("r") as sched_file: sched_header_line = sched_file.readline() # The first line contains the outer PID - except FileNotFoundError: + except (FileNotFoundError, ProcessLookupError): # That's OK, processes might disappear before we get the chance to handle them continue From 85a3e8a27d660b7682dd320c69764c192ee880c7 Mon Sep 17 00:00:00 2001 From: Adi Benziony Date: Sun, 16 Jan 2022 20:05:26 +0200 Subject: [PATCH 11/19] get_process_nspid() returns int or raises Also, make sure we raise NoSuchProcess in all places instead of weird, un-indicative errors --- granulate_utils/linux/ns.py | 48 +++++++++++++++++++++++++++---------- 1 file changed, 36 insertions(+), 12 deletions(-) diff --git a/granulate_utils/linux/ns.py b/granulate_utils/linux/ns.py index 721ca4d4..c6d2e1f9 100644 --- a/granulate_utils/linux/ns.py +++ b/granulate_utils/linux/ns.py @@ -68,12 +68,13 @@ def resolve_proc_root_links(proc_root: str, ns_path: str) -> str: return path -def get_process_nspid(pid: int) -> Optional[int]: - with open(f"/proc/{pid}/status") as f: - for line in f: - fields = line.split() - if fields[0] == "NSpid:": - return int(fields[-1]) +def get_process_nspid(pid: int) -> int: + """ + :raises NoSuchProcess: If the process doesn't or no longer exists + """ + nspid = _get_process_nspid_by_status_file(pid) + if nspid is not None: + return nspid if is_same_ns(pid, NsType.pid.name): # If we're in the same PID namespace, then the outer PID is also the inner pid (NSpid) @@ -82,7 +83,20 @@ def get_process_nspid(pid: int) -> Optional[int]: return _get_process_nspid_by_sched_files(pid) -def _get_process_nspid_by_sched_files(pid: int): +def _get_process_nspid_by_status_file(pid: int) -> Optional[int]: + try: + with open(f"/proc/{pid}/status") as f: + for line in f: + fields = line.split() + if fields[0] == "NSpid:": + return int(fields[-1]) + + return None + except (FileNotFoundError, ProcessLookupError) as e: + raise NoSuchProcess(pid) from e + + +def _get_process_nspid_by_sched_files(pid: int) -> int: # Old kernel (pre 4.1) doesn't have an NSpid field in their /proc/pid/status file # Instead, we can look through all /proc/*/sched files from inside the process' pid namespace, and due to a bug # (fixed in 4.14) the outer PID is exposed, so we can find the target process by comparing the outer PID @@ -114,14 +128,24 @@ def _find_inner_pid() -> Optional[int]: # We're searching `/proc`, so we only need to set our mount namespace inner_pid = run_in_ns(["mnt"], _find_inner_pid, pid) - return inner_pid + if inner_pid is not None: + return inner_pid + + # If we weren't able to find the process' nspid, he must have been killed while searching + assert not Path(f'/proc/{pid}').exists(), f"Process {pid} is running, but we failed to find his nspid" + + raise NoSuchProcess(pid) def is_same_ns(pid: int, nstype: str, pid2: int = None) -> bool: - return ( - os.stat(f"/proc/{pid2 if pid2 is not None else 'self'}/ns/{nstype}").st_ino - == os.stat(f"/proc/{pid}/ns/{nstype}").st_ino - ) + return get_process_ns_inode(pid2 if pid2 is not None else 'self', nstype) == get_process_ns_inode(pid, nstype) + + +def get_process_ns_inode(pid: Union[int, str], nstype: str): + try: + return os.stat(f"/proc/{pid}/ns/{nstype}").st_ino + except FileNotFoundError as e: + raise NoSuchProcess(pid) from e def run_in_ns(nstypes: List[str], callback: Callable[[], T], target_pid: int = 1) -> T: From c28f0da012271610664df9105bc0e463265b14e8 Mon Sep 17 00:00:00 2001 From: Adi Benziony Date: Mon, 17 Jan 2022 11:08:07 +0200 Subject: [PATCH 12/19] Fix formatting --- granulate_utils/linux/ns.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/granulate_utils/linux/ns.py b/granulate_utils/linux/ns.py index c6d2e1f9..22d5cc65 100644 --- a/granulate_utils/linux/ns.py +++ b/granulate_utils/linux/ns.py @@ -132,13 +132,13 @@ def _find_inner_pid() -> Optional[int]: return inner_pid # If we weren't able to find the process' nspid, he must have been killed while searching - assert not Path(f'/proc/{pid}').exists(), f"Process {pid} is running, but we failed to find his nspid" + assert not Path(f"/proc/{pid}").exists(), f"Process {pid} is running, but we failed to find his nspid" raise NoSuchProcess(pid) def is_same_ns(pid: int, nstype: str, pid2: int = None) -> bool: - return get_process_ns_inode(pid2 if pid2 is not None else 'self', nstype) == get_process_ns_inode(pid, nstype) + return get_process_ns_inode(pid2 if pid2 is not None else "self", nstype) == get_process_ns_inode(pid, nstype) def get_process_ns_inode(pid: Union[int, str], nstype: str): From 00bf3667a1014b0b2fd79d8458a19211f604c4d4 Mon Sep 17 00:00:00 2001 From: Adi Benziony Date: Mon, 17 Jan 2022 12:35:15 +0200 Subject: [PATCH 13/19] Make enable_hook.sh work in submodules as well --- enable_hook.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/enable_hook.sh b/enable_hook.sh index af16db7c..58c5e8b9 100755 --- a/enable_hook.sh +++ b/enable_hook.sh @@ -1,4 +1,4 @@ #!/bin/bash set -e -ln -s "../../lint.sh" "$(git rev-parse --show-toplevel)/.git/hooks/pre-commit" +ln -s "$(git rev-parse --show-toplevel)/lint.sh" "$(git rev-parse --git-path hooks)/pre-commit" From edfd78da015e3457a0a91d782f8b528482347a85 Mon Sep 17 00:00:00 2001 From: Adi Benziony Date: Mon, 17 Jan 2022 17:59:58 +0200 Subject: [PATCH 14/19] Handle all pid reuse cases --- granulate_utils/linux/ns.py | 63 ++++++++++++++++++++++++++----------- 1 file changed, 44 insertions(+), 19 deletions(-) diff --git a/granulate_utils/linux/ns.py b/granulate_utils/linux/ns.py index 22d5cc65..9145ddf0 100644 --- a/granulate_utils/linux/ns.py +++ b/granulate_utils/linux/ns.py @@ -68,35 +68,42 @@ def resolve_proc_root_links(proc_root: str, ns_path: str) -> str: return path -def get_process_nspid(pid: int) -> int: +def get_process_nspid(process: Union[Process, int]) -> int: """ :raises NoSuchProcess: If the process doesn't or no longer exists """ - nspid = _get_process_nspid_by_status_file(pid) + if isinstance(process, int): + process = Process(process) + + nspid = _get_process_nspid_by_status_file(process) if nspid is not None: return nspid - if is_same_ns(pid, NsType.pid.name): + if is_same_ns(process, NsType.pid.name): # If we're in the same PID namespace, then the outer PID is also the inner pid (NSpid) - return pid + return process.pid - return _get_process_nspid_by_sched_files(pid) + return _get_process_nspid_by_sched_files(process) -def _get_process_nspid_by_status_file(pid: int) -> Optional[int]: +def _get_process_nspid_by_status_file(process: Process) -> Optional[int]: try: - with open(f"/proc/{pid}/status") as f: + with open(f"/proc/{process.pid}/status") as f: + # If the process isn't running, then we opened the wrong `status` file + if not process.is_running(): + raise NoSuchProcess(process.pid) + for line in f: fields = line.split() if fields[0] == "NSpid:": - return int(fields[-1]) + return int(fields[-1]) # The last pid in the list is the innermost pid, according to `man 5 proc` return None except (FileNotFoundError, ProcessLookupError) as e: - raise NoSuchProcess(pid) from e + raise NoSuchProcess(process.pid) from e -def _get_process_nspid_by_sched_files(pid: int) -> int: +def _get_process_nspid_by_sched_files(process: Process) -> int: # Old kernel (pre 4.1) doesn't have an NSpid field in their /proc/pid/status file # Instead, we can look through all /proc/*/sched files from inside the process' pid namespace, and due to a bug # (fixed in 4.14) the outer PID is exposed, so we can find the target process by comparing the outer PID @@ -121,31 +128,49 @@ def _find_inner_pid() -> Optional[int]: match = pattern.search(sched_header_line) if match is not None: outer_pid = int(match.group(1)) - if outer_pid == pid: + if outer_pid == process.pid: return int(procfs_child.name) return None # We're searching `/proc`, so we only need to set our mount namespace - inner_pid = run_in_ns(["mnt"], _find_inner_pid, pid) + inner_pid = run_in_ns(["mnt"], _find_inner_pid, process.pid) if inner_pid is not None: + if not process.is_running(): # Make sure the pid wasn't reused for another process + raise NoSuchProcess(process.pid) + return inner_pid # If we weren't able to find the process' nspid, he must have been killed while searching - assert not Path(f"/proc/{pid}").exists(), f"Process {pid} is running, but we failed to find his nspid" + assert not process.is_running(), f"Process {process.pid} is running, but we failed to find his nspid" + + raise NoSuchProcess(process.pid) + + +def is_same_ns(process: Union[Process, int], nstype: str, process2: Union[Process, int] = None) -> bool: + if isinstance(process, int): + process = Process(process) + if isinstance(process2, int): + process2 = Process(process2) + elif process2 is None: + process2 = Process() # `self` - raise NoSuchProcess(pid) + is_the_same_ns = get_process_ns_inode(process, nstype) == get_process_ns_inode(process2, nstype) + # If one of the processes isn't running, we checked the wrong one + if not process.is_running(): + raise NoSuchProcess(process.pid) + if not process2.is_running(): + raise NoSuchProcess(process2.pid) -def is_same_ns(pid: int, nstype: str, pid2: int = None) -> bool: - return get_process_ns_inode(pid2 if pid2 is not None else "self", nstype) == get_process_ns_inode(pid, nstype) + return is_the_same_ns -def get_process_ns_inode(pid: Union[int, str], nstype: str): +def get_process_ns_inode(process: Process, nstype: str): try: - return os.stat(f"/proc/{pid}/ns/{nstype}").st_ino + return os.stat(f"/proc/{process.pid}/ns/{nstype}").st_ino except FileNotFoundError as e: - raise NoSuchProcess(pid) from e + raise NoSuchProcess(process.pid) from e def run_in_ns(nstypes: List[str], callback: Callable[[], T], target_pid: int = 1) -> T: From 0a9bd818587a2bba6911f4f5dff1d3ddbb571edf Mon Sep 17 00:00:00 2001 From: Adi Benziony Date: Mon, 17 Jan 2022 18:12:01 +0200 Subject: [PATCH 15/19] Add comment refering to zombie processes in searching sched files --- granulate_utils/linux/ns.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/granulate_utils/linux/ns.py b/granulate_utils/linux/ns.py index 9145ddf0..7c3a9f3c 100644 --- a/granulate_utils/linux/ns.py +++ b/granulate_utils/linux/ns.py @@ -141,7 +141,8 @@ def _find_inner_pid() -> Optional[int]: return inner_pid - # If we weren't able to find the process' nspid, he must have been killed while searching + # If we weren't able to find the process' nspid, he must have been killed while searching (we only search + # `/proc/pid/sched` files, and they exist as long as the process is running (including zombie processes) assert not process.is_running(), f"Process {process.pid} is running, but we failed to find his nspid" raise NoSuchProcess(process.pid) From 65b0a25d812551940b3c70622fee71250c6a96f8 Mon Sep 17 00:00:00 2001 From: Adi Benziony Date: Wed, 19 Jan 2022 11:56:10 +0200 Subject: [PATCH 16/19] Properly handle unsupported nstype for older kernels --- granulate_utils/exceptions.py | 4 ++++ granulate_utils/linux/ns.py | 29 ++++++++++++++++++----------- 2 files changed, 22 insertions(+), 11 deletions(-) create mode 100644 granulate_utils/exceptions.py diff --git a/granulate_utils/exceptions.py b/granulate_utils/exceptions.py new file mode 100644 index 00000000..488b53d2 --- /dev/null +++ b/granulate_utils/exceptions.py @@ -0,0 +1,4 @@ +class UnsupportedNamespaceError(Exception): + def __init__(self, nstype: str): + super().__init__(f"Namespace '{nstype}' is not supported by this kernel") + self.nstype = nstype diff --git a/granulate_utils/linux/ns.py b/granulate_utils/linux/ns.py index 7c3a9f3c..06f16a47 100644 --- a/granulate_utils/linux/ns.py +++ b/granulate_utils/linux/ns.py @@ -13,6 +13,8 @@ from psutil import NoSuchProcess, Process +from granulate_utils.exceptions import UnsupportedNamespaceError + T = TypeVar("T") HOST_ROOT_PREFIX = "/proc/1/root" @@ -156,22 +158,27 @@ def is_same_ns(process: Union[Process, int], nstype: str, process2: Union[Proces elif process2 is None: process2 = Process() # `self` - is_the_same_ns = get_process_ns_inode(process, nstype) == get_process_ns_inode(process2, nstype) - - # If one of the processes isn't running, we checked the wrong one - if not process.is_running(): - raise NoSuchProcess(process.pid) - if not process2.is_running(): - raise NoSuchProcess(process2.pid) - - return is_the_same_ns + try: + return get_process_ns_inode(process, nstype) == get_process_ns_inode(process2, nstype) + except UnsupportedNamespaceError: + # The namespace does not exist in this kernel, hence the two processes are logically in the same namespace + return True def get_process_ns_inode(process: Process, nstype: str): try: - return os.stat(f"/proc/{process.pid}/ns/{nstype}").st_ino + ns_inode = os.stat(f"/proc/{process.pid}/ns/{nstype}").st_ino except FileNotFoundError as e: - raise NoSuchProcess(process.pid) from e + if process.is_running(): + raise UnsupportedNamespaceError(nstype) from e + else: + raise NoSuchProcess(process.pid) from e + + # If the process isn't running, we checked the wrong one + if not process.is_running(): + raise NoSuchProcess(process.pid) + + return ns_inode def run_in_ns(nstypes: List[str], callback: Callable[[], T], target_pid: int = 1) -> T: From b5e83772f5144684007ed0b376d553ed6bc80964 Mon Sep 17 00:00:00 2001 From: Adi Benziony Date: Wed, 19 Jan 2022 12:19:00 +0200 Subject: [PATCH 17/19] Make get_process_ns_inode() private --- granulate_utils/linux/ns.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/granulate_utils/linux/ns.py b/granulate_utils/linux/ns.py index 06f16a47..7dc6806d 100644 --- a/granulate_utils/linux/ns.py +++ b/granulate_utils/linux/ns.py @@ -159,13 +159,13 @@ def is_same_ns(process: Union[Process, int], nstype: str, process2: Union[Proces process2 = Process() # `self` try: - return get_process_ns_inode(process, nstype) == get_process_ns_inode(process2, nstype) + return _get_process_ns_inode(process, nstype) == _get_process_ns_inode(process2, nstype) except UnsupportedNamespaceError: # The namespace does not exist in this kernel, hence the two processes are logically in the same namespace return True -def get_process_ns_inode(process: Process, nstype: str): +def _get_process_ns_inode(process: Process, nstype: str): try: ns_inode = os.stat(f"/proc/{process.pid}/ns/{nstype}").st_ino except FileNotFoundError as e: From 7f8fb31e64618843b9d5a1a689f98e245b208cd6 Mon Sep 17 00:00:00 2001 From: Adi Benziony Date: Wed, 19 Jan 2022 15:53:32 +0200 Subject: [PATCH 18/19] Typo in doc Co-authored-by: Yonatan Goldschmidt --- granulate_utils/linux/ns.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/granulate_utils/linux/ns.py b/granulate_utils/linux/ns.py index 7dc6806d..a9706cce 100644 --- a/granulate_utils/linux/ns.py +++ b/granulate_utils/linux/ns.py @@ -143,7 +143,7 @@ def _find_inner_pid() -> Optional[int]: return inner_pid - # If we weren't able to find the process' nspid, he must have been killed while searching (we only search + # If we weren't able to find the process' nspid, it must have been killed while searching (we only search # `/proc/pid/sched` files, and they exist as long as the process is running (including zombie processes) assert not process.is_running(), f"Process {process.pid} is running, but we failed to find his nspid" From a8651379a46a5f1d31882b158387731a3ac97663 Mon Sep 17 00:00:00 2001 From: Adi Benziony Date: Wed, 19 Jan 2022 15:54:41 +0200 Subject: [PATCH 19/19] Use repr to print string Co-authored-by: Yonatan Goldschmidt --- granulate_utils/exceptions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/granulate_utils/exceptions.py b/granulate_utils/exceptions.py index 488b53d2..f7627450 100644 --- a/granulate_utils/exceptions.py +++ b/granulate_utils/exceptions.py @@ -1,4 +1,4 @@ class UnsupportedNamespaceError(Exception): def __init__(self, nstype: str): - super().__init__(f"Namespace '{nstype}' is not supported by this kernel") + super().__init__(f"Namespace {nstype!r} is not supported by this kernel") self.nstype = nstype