13
13
from subprocess import CompletedProcess
14
14
from threading import Event , Lock
15
15
from types import TracebackType
16
- from typing import Any , Dict , Iterable , List , Optional , Sequence , Set , Type , TypeVar , Union
16
+ from typing import Any , Dict , Iterable , List , Optional , Set , Type , TypeVar , Union
17
17
18
18
import psutil
19
19
from granulate_utils .java import (
79
79
touch_path ,
80
80
wait_event ,
81
81
)
82
- from gprofiler .utils .fs import is_rw_exec_dir , safe_copy
82
+ from gprofiler .utils .fs import is_owned_by_root , is_rw_exec_dir , mkdir_owned_root , safe_copy
83
83
from gprofiler .utils .perf import can_i_use_perf_events
84
84
from gprofiler .utils .process import process_comm , search_proc_maps
85
85
@@ -471,9 +471,10 @@ def __init__(
471
471
# because storage_dir changes between runs.
472
472
# we embed the async-profiler version in the path, so future gprofiler versions which use another version
473
473
# of AP case use it (will be loaded as a different DSO)
474
+ self ._ap_dir_base = self ._find_rw_exec_dir ()
475
+ self ._ap_dir_versioned = os .path .join (self ._ap_dir_base , f"async-profiler-{ get_ap_version ()} " )
474
476
self ._ap_dir_host = os .path .join (
475
- self ._find_rw_exec_dir (POSSIBLE_AP_DIRS ),
476
- f"async-profiler-{ get_ap_version ()} " ,
477
+ self ._ap_dir_versioned ,
477
478
"musl" if self ._needs_musl_ap () else "glibc" ,
478
479
)
479
480
@@ -498,20 +499,42 @@ def __init__(
498
499
self ._collect_meminfo = collect_meminfo
499
500
self ._include_method_modifiers = ",includemm" if include_method_modifiers else ""
500
501
501
- def _find_rw_exec_dir (self , available_dirs : Sequence [ str ] ) -> str :
502
+ def _find_rw_exec_dir (self ) -> str :
502
503
"""
503
504
Find a rw & executable directory (in the context of the process) where we can place libasyncProfiler.so
504
505
and the target process will be able to load it.
506
+ This function creates the gprofiler_tmp directory as a directory owned by root, if it doesn't exist under the
507
+ chosen rwx directory.
508
+ It does not create the parent directory itself, if it doesn't exist (e.g /run).
509
+ The chosen rwx directory needs to be owned by root.
505
510
"""
506
- for d in available_dirs :
507
- full_dir = resolve_proc_root_links (self ._process_root , d )
511
+ for d in POSSIBLE_AP_DIRS :
512
+ full_dir = Path (resolve_proc_root_links (self ._process_root , d ))
513
+ if not full_dir .parent .exists ():
514
+ continue # we do not create the parent.
515
+
516
+ if not is_owned_by_root (full_dir .parent ):
517
+ continue # the parent needs to be owned by root
518
+
519
+ mkdir_owned_root (full_dir )
520
+
508
521
if is_rw_exec_dir (full_dir ):
509
- return full_dir
522
+ return str ( full_dir )
510
523
else :
511
- raise NoRwExecDirectoryFoundError (f"Could not find a rw & exec directory out of { available_dirs } !" )
524
+ raise NoRwExecDirectoryFoundError (
525
+ f"Could not find a rw & exec directory out of { POSSIBLE_AP_DIRS } for { self ._process_root } !"
526
+ )
512
527
513
528
def __enter__ (self : T ) -> T :
514
- os .makedirs (self ._ap_dir_host , 0o755 , exist_ok = True )
529
+ # create the directory structure for executable libap, make sure it's owned by root
530
+ # for sanity & simplicity, mkdir_owned_root() does not support creating parent directories, as this allows
531
+ # the caller to absentmindedly ignore the check of the parents ownership.
532
+ # hence we create the structure here part by part.
533
+ assert is_owned_by_root (
534
+ Path (self ._ap_dir_base )
535
+ ), f"expected { self ._ap_dir_base } to be owned by root at this point"
536
+ mkdir_owned_root (self ._ap_dir_versioned )
537
+ mkdir_owned_root (self ._ap_dir_host )
515
538
os .makedirs (self ._storage_dir_host , 0o755 , exist_ok = True )
516
539
517
540
self ._check_disk_requirements ()
0 commit comments