Is std::sync::Once some how tied/reset to a process id or process clone operation?

Question: When lazy_static vairable/initializtion and Once::call_once() code get executed and when does it gets reset, and rexecuted, relate to a clone/fork or execv* syscall/

Code inside call_once is expected to be executed only once. But once per, what? process?
How does the clone/fork system call, or execv* system call affect whether the code gets executed?

use std::sync::Once;

static START: Once = Once::new();

START.call_once(|| {
    println!("Hello World")
    // run initialization here
});

Specifically, within a process that has done START.call_once() already, will the code in START.call_once() get executed again after syscall clone? How about after execv syscall?
What is the boundary that resets this and causes the code to be executed again.

I suspect this also applies to when lazy_static!{} variables are initialized or reinitialized.

I am building LD_PRELOAD program.

I have some one time initialization code like opening a tracker file to write filesystem access meta data to it. We also create UUID typically once per process. I put in some tracing out put to track what happens.
I also made the one time initialization code is invoked from multiple locations, including the library constructor functon, the intercepted execv* functions.

The following traceback suggests, the one time initialization code gets executed once from the constructor function, then after I suspect a clone syscall, that forks a new PID, shown in "WISKTRACK Initializer Start: false 25872"

The UUID is printed for every line of trace and supposed to be created once process. And UUID lazy_static doesnt seem to be reinitialized as part of the clone syscall and doesnt change after PID change.
But the TRACKER lazy_static seems to reinitialized/created after the clone/fork sys call as seen by the logs.

VeStgUiiyEa0KVLIPVP61F: initialize_constructor_statics:

VeStgUiiyEa0KVLIPVP61F: initialize_constructor_statics: Complete

VeStgUiiyEa0KVLIPVP61F: WISKTRACK Initializer Start: false 25872

VeStgUiiyEa0KVLIPVP61F: WISKTRACK Initializing: false

VeStgUiiyEa0KVLIPVP61F: initialize_statics:

VeStgUiiyEa0KVLIPVP61F: Config Reading....

VeStgUiiyEa0KVLIPVP61F: CONFIG Reading....Done

VeStgUiiyEa0KVLIPVP61F: APP64BITONLY_PATTERNS Reading....

VeStgUiiyEa0KVLIPVP61F: p: ["^/ws/sarvi\\-sjc/wisktrack/asdadasd/asdai-++.sdad"]

VeStgUiiyEa0KVLIPVP61F: APP64BITONLY_PATTERNS Reading....Done

VeStgUiiyEa0KVLIPVP61F: Tracker Create


VeStgUiiyEa0KVLIPVP61F: Tracker Initializer: 800

VeStgUiiyEa0KVLIPVP61F: Tracker Initialization 1: false

VeStgUiiyEa0KVLIPVP61F: Tracker Initialization 2: false

VeStgUiiyEa0KVLIPVP61F: Tracker Initialization 3: true

VeStgUiiyEa0KVLIPVP61F: Tracker Create Done


VeStgUiiyEa0KVLIPVP61F: initialize_statics: Complete

VeStgUiiyEa0KVLIPVP61F: Tracker Initialization 1: true

VeStgUiiyEa0KVLIPVP61F: Tracker Initialization 3: true

VeStgUiiyEa0KVLIPVP61F: Tracker Initializer Complete true

The above happens inside the cosntructor function.
This same once initialization is also invoked inside an execv* iterept boundary before the syscall is invoked.
The following happens as part of the execv* interception, before the syscall. But I suspect a process clone happenned as the PID has changed from 25872 to 25874. The think both lazy static initializations and std::sync::Once wrapped code are executed again.

VeStgUiiyEa0KVLIPVP61F: execve(VeStgUiiyEa0KVLIPVP61F, /usr/bin/ls, "ls NULL")

VeStgUiiyEa0KVLIPVP61F: execve: VeStgUiiyEa0KVLIPVP61F: Updated Env {"PWD": "/ws/sarvi-sjc/wisktrack", "LD_PRELOAD": "/ws/sarvi-sjc/wisktrack/${LIB}/libwisktrack.so", "RUST_BACKTRACE": "1", "WISK_WSROOT": "/ws/sarvi-sjc/wisktrack", "HOME": "/users/sarvi", "WISK_CONFIG": "", "PATH": "/users/sarvi/.cargo/bin:/auto/binos-tools/bin:/router/bin:/usr/cisco/bin:/usr/atria/bin:/usr/bin:/usr/local/bin:/usr/local/etc:/bin:/usr/X11R6/bin:/usr/sbin:/sbin:/usr/bin:/auto/nova-env/Lab/Labpatch/bin", "SHLVL": "1", "TERM": "xterm-256color", "WISK_TRACK": "", "_": "/usr/bin/ls", "WISK_PUUID": "VeStgUiiyEa0KVLIPVP61F", "WISK_TRACE": "", "USER": "sarvi"}

VeStgUiiyEa0KVLIPVP61F: WISKTRACK Initializer Start: true 25874

VeStgUiiyEa0KVLIPVP61F: Tracker Initializer Complete true


U6dZUjfrIAZWoKTFv9OtH9: initialize_constructor_statics:

U6dZUjfrIAZWoKTFv9OtH9: initialize_constructor_statics: Complete

U6dZUjfrIAZWoKTFv9OtH9: WISKTRACK Initializer Start: false 25874

U6dZUjfrIAZWoKTFv9OtH9: WISKTRACK Initializing: false

U6dZUjfrIAZWoKTFv9OtH9: initialize_statics:

U6dZUjfrIAZWoKTFv9OtH9: Config Reading....

U6dZUjfrIAZWoKTFv9OtH9: CONFIG Reading....Done

U6dZUjfrIAZWoKTFv9OtH9: APP64BITONLY_PATTERNS Reading....

U6dZUjfrIAZWoKTFv9OtH9: p: ["^/ws/sarvi\\-sjc/wisktrack/asdadasd/asdai-++.sdad"]

U6dZUjfrIAZWoKTFv9OtH9: APP64BITONLY_PATTERNS Reading....Done

U6dZUjfrIAZWoKTFv9OtH9: Tracker Create


U6dZUjfrIAZWoKTFv9OtH9: Tracker Initializer: 800

U6dZUjfrIAZWoKTFv9OtH9: Tracker Initialization 1: false

U6dZUjfrIAZWoKTFv9OtH9: Tracker Initialization 2: false

U6dZUjfrIAZWoKTFv9OtH9: Tracker Initialization 3: true

U6dZUjfrIAZWoKTFv9OtH9: Tracker Create Done


U6dZUjfrIAZWoKTFv9OtH9: initialize_statics: Complete

U6dZUjfrIAZWoKTFv9OtH9: Tracker Initialization 1: true

U6dZUjfrIAZWoKTFv9OtH9: Tracker Initialization 3: true

U6dZUjfrIAZWoKTFv9OtH9: Tracker Initializer Complete true


Cargo.lock  Makefile   core.bash.25110  lib32    src     t.diff  test     wisk
Cargo.toml  README.md  core.bash.25485  lib64    strace  t.sh    test.sh  wisktrack.file
LICENSE     config     lib              scripts  t       target  tests
VeStgUiiyEa0KVLIPVP61F: execve(VeStgUiiyEa0KVLIPVP61F, /usr/bin/ls, "ls NULL")

VeStgUiiyEa0KVLIPVP61F: execve: VeStgUiiyEa0KVLIPVP61F: Updated Env {"PWD": "/ws/sarvi-sjc/wisktrack", "LD_PRELOAD": "/ws/sarvi-sjc/wisktrack/${LIB}/libwisktrack.so", "RUST_BACKTRACE": "1", "WISK_WSROOT": "/ws/sarvi-sjc/wisktrack", "HOME": "/users/sarvi", "WISK_CONFIG": "", "PATH": "/users/sarvi/.cargo/bin:/auto/binos-tools/bin:/router/bin:/usr/cisco/bin:/usr/atria/bin:/usr/bin:/usr/local/bin:/usr/local/etc:/bin:/usr/X11R6/bin:/usr/sbin:/sbin:/usr/bin:/auto/nova-env/Lab/Labpatch/bin", "SHLVL": "1", "TERM": "xterm-256color", "WISK_TRACK": "", "_": "/usr/bin/ls", "WISK_PUUID": "VeStgUiiyEa0KVLIPVP61F", "WISK_TRACE": "", "USER": "sarvi"}

VeStgUiiyEa0KVLIPVP61F: WISKTRACK Initializer Start: true 25875

VeStgUiiyEa0KVLIPVP61F: Tracker Initializer Complete true


3GO5kxWacAM07nLRGqYh59: initialize_constructor_statics:

3GO5kxWacAM07nLRGqYh59: initialize_constructor_statics: Complete

3GO5kxWacAM07nLRGqYh59: WISKTRACK Initializer Start: false 25875

3GO5kxWacAM07nLRGqYh59: WISKTRACK Initializing: false

3GO5kxWacAM07nLRGqYh59: initialize_statics:

3GO5kxWacAM07nLRGqYh59: Config Reading....

3GO5kxWacAM07nLRGqYh59: CONFIG Reading....Done

3GO5kxWacAM07nLRGqYh59: APP64BITONLY_PATTERNS Reading....

3GO5kxWacAM07nLRGqYh59: p: ["^/ws/sarvi\\-sjc/wisktrack/asdadasd/asdai-++.sdad"]

3GO5kxWacAM07nLRGqYh59: APP64BITONLY_PATTERNS Reading....Done

3GO5kxWacAM07nLRGqYh59: Tracker Create


3GO5kxWacAM07nLRGqYh59: Tracker Initializer: 800

3GO5kxWacAM07nLRGqYh59: Tracker Initialization 1: false

3GO5kxWacAM07nLRGqYh59: Tracker Initialization 2: false

3GO5kxWacAM07nLRGqYh59: Tracker Initialization 3: true

3GO5kxWacAM07nLRGqYh59: Tracker Create Done


3GO5kxWacAM07nLRGqYh59: initialize_statics: Complete

3GO5kxWacAM07nLRGqYh59: Tracker Initialization 1: true

3GO5kxWacAM07nLRGqYh59: Tracker Initialization 3: true

3GO5kxWacAM07nLRGqYh59: Tracker Initializer Complete true

Instead of deleting the thread. I thought I would post what I found.

I suspect because the code was doing fork/exec operation, the fork spawn a new process and the execv* ad its interception happenned within that process. hence nothing happened in the context of the first process. The Once code was cloned into the sub process and executed.

So though the Once code/variable was created in the parent process, it was first executed during the fork/exec, after fork and before exec. So this got executed everytime this fork/exec happenned, because it was happenning in its own fork process space.

I am looking to do some one time initialization, such as opening some files and writing some 1 time per exec'ed pocess metadata, preferably from not within the constructor, which seems cause other unpredictable side effects, specially rust code.
Has caused lots of segfaults with nothing useful in the coredump or backtrace to help me debug.

So I am trying to keep the amount of rust filesystem operations to a minimum.
Or advice on how best to debug/trouble shoot such probles,

And I need a way to get this one time initialization code executed as part of the main process. Is there some libc system call that always gets executed that I can intercept and leverage to do this 1 time initialization?

Maybe you can register a prepare handler in pthread_atfork? Note that this doesn't get called for vfork though.

Thats a nice idea. That will certainly guarantee what I am looking for.

I libc has so

pub unsafe extern "C" fn pthread_atfork(
    prepare: Option<unsafe extern "C" fn()>, 
    parent: Option<unsafe extern "C" fn()>, 
    child: Option<unsafe extern "C" fn()>
) -> c_int

Just to be sure i have enough to implement this. I suspect the following is a function prototype that I can use with the above libc API call.

pub unsafe extern "C" fn C_functionn (v1 : libc::c_int, v2: libc::c_char ) -> libc_c_int {
}

Here's an experiment to test how fork and Once interact:

use libc::{self, pid_t};
use std::sync::Once;

static mut INITIALIZING_PID: pid_t = -1;

macro_rules! log {
    ($format:literal $(, $args:expr)*) => {
        println!(concat!("[{}] ", $format), libc::getpid(), $($args)*);
    }
}

fn main() {
    unsafe {
        log!("Started");
        let init = Once::new();

        init.call_once(|| {
            let pid = libc::getpid();
            log!("Initializing");
            INITIALIZING_PID = pid;
        });

        let pid = libc::fork();
        assert!(pid >= 0, "Fork Failed");

       log!("Forked and got {}", pid);
        
        init.call_once(|| {
            log!("Trying to initialize again");
            INITIALIZING_PID = libc::getpid();
        });

        if pid == 0 {
            log!("Reading from child: {}", INITIALIZING_PID);
        } else {
            log!("Reading from parent: {}", INITIALIZING_PID);
            // wait for the child to exit
            let mut return_code = 0;
            libc::waitpid(pid, &mut return_code, 0);
            log!("Child exited with {}", return_code);
        }
    }
}

(playground)

And it outputs the following:

[8] Started
[8] Initializing
[8] Forked and got 456
[8] Reading from parent: 8
[456] Forked and got 0
[456] Reading from child: 8
[8] Child exited with 0

It looks like if the Once was initialized before the fork, its value will be retained and it won't re-run the protected code.

I my case init.call_once(|| {}) is called in my execv* intecept code.
Os I suspect it let init = Once::new(); happens in process parent.

init.call_once(|| {}) gets called in the execv* intercept code, after a new process is forked.
That explains the behavior I am seeing.