    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149

    //! Job management (mostly for windows)
    //!
    //! Most of the time when you're running cargo you expect Ctrl-C to actually
    //! terminate the entire tree of processes in play, not just the one at the top
    //! (cargo). This currently works "by default" on Unix platforms because Ctrl-C
    //! actually sends a signal to the *process group* rather than the parent
    //! process, so everything will get torn down. On Windows, however, this does
    //! not happen and Ctrl-C just kills cargo.
    //!
    //! To achieve the same semantics on Windows we use Job Objects to ensure that
    //! all processes die at the same time. Job objects have a mode of operation
    //! where when all handles to the object are closed it causes all child
    //! processes associated with the object to be terminated immediately.
    //! Conveniently whenever a process in the job object spawns a new process the
    //! child will be associated with the job object as well. This means if we add
    //! ourselves to the job object we create then everything will get torn down!

    pub use self::imp::Setup;

    pub fn setup() -> Option<Setup> {
        unsafe { imp::setup() }
    }

    #[cfg(unix)]
    mod imp {
        use std::env;

        pub type Setup = ();

        pub unsafe fn setup() -> Option<()> {
            // There's a test case for the behavior of
            // when-cargo-is-killed-subprocesses-are-also-killed, but that requires
            // one cargo spawned to become its own session leader, so we do that
            // here.
            //
            // ALLOWED: For testing cargo itself only.
            #[allow(clippy::disallowed_methods)]
            if env::var("__CARGO_TEST_SETSID_PLEASE_DONT_USE_ELSEWHERE").is_ok() {
                libc::setsid();
            }
            Some(())
        }
    }

    #[cfg(windows)]
    mod imp {
        use std::io;
        use std::mem;
        use std::ptr;
        use std::ptr::addr_of;

        use tracing::info;

        use windows_sys::Win32::Foundation::CloseHandle;
        use windows_sys::Win32::Foundation::HANDLE;
        use windows_sys::Win32::Foundation::INVALID_HANDLE_VALUE;
        use windows_sys::Win32::System::JobObjects::AssignProcessToJobObject;
        use windows_sys::Win32::System::JobObjects::CreateJobObjectW;
        use windows_sys::Win32::System::JobObjects::JobObjectExtendedLimitInformation;
        use windows_sys::Win32::System::JobObjects::SetInformationJobObject;
        use windows_sys::Win32::System::JobObjects::JOBOBJECT_EXTENDED_LIMIT_INFORMATION;
        use windows_sys::Win32::System::JobObjects::JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
        use windows_sys::Win32::System::Threading::GetCurrentProcess;

        pub struct Setup {
            job: Handle,
        }

        pub struct Handle {
            inner: HANDLE,
        }

        fn last_err() -> io::Error {
            io::Error::last_os_error()
        }

        pub unsafe fn setup() -> Option<Setup> {
            // Creates a new job object for us to use and then adds ourselves to it.
            // Note that all errors are basically ignored in this function,
            // intentionally. Job objects are "relatively new" in Windows,
            // particularly the ability to support nested job objects. Older
            // Windows installs don't support this ability. We probably don't want
            // to force Cargo to abort in this situation or force others to *not*
            // use job objects, so we instead just ignore errors and assume that
            // we're otherwise part of someone else's job object in this case.

            let job = CreateJobObjectW(ptr::null_mut(), ptr::null());
            if job == INVALID_HANDLE_VALUE {
                return None;
            }
            let job = Handle { inner: job };

            // Indicate that when all handles to the job object are gone that all
            // process in the object should be killed. Note that this includes our
            // entire process tree by default because we've added ourselves and
            // our children will reside in the job once we spawn a process.
            let mut info: JOBOBJECT_EXTENDED_LIMIT_INFORMATION;
            info = mem::zeroed();
            info.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
            let r = SetInformationJobObject(
                job.inner,
                JobObjectExtendedLimitInformation,
                addr_of!(info) as *const _,
                mem::size_of_val(&info) as u32,
            );
            if r == 0 {
                return None;
            }

            // Assign our process to this job object, meaning that our children will
            // now live or die based on our existence.
            let me = GetCurrentProcess();
            let r = AssignProcessToJobObject(job.inner, me);
            if r == 0 {
                return None;
            }

            Some(Setup { job })
        }

        impl Drop for Setup {
            fn drop(&mut self) {
                // On normal exits (not ctrl-c), we don't want to kill any child
                // processes. The destructor here configures our job object to
                // **not** kill everything on close, then closes the job object.
                unsafe {
                    let info: JOBOBJECT_EXTENDED_LIMIT_INFORMATION;
                    info = mem::zeroed();
                    let r = SetInformationJobObject(
                        self.job.inner,
                        JobObjectExtendedLimitInformation,
                        addr_of!(info) as *const _,
                        mem::size_of_val(&info) as u32,
                    );
                    if r == 0 {
                        info!("failed to configure job object to defaults: {}", last_err());
                    }
                }
            }
        }

        impl Drop for Handle {
            fn drop(&mut self) {
                unsafe {
                    CloseHandle(self.inner);
                }
            }
        }
    }