Track system event(shutdown/restart) via launchagent

Hi There,

I have to achieve following scenario

Track system event on macosx for shutdown and restart and update one plist with same event via launchAgent

I have tried following code on launchAgent

class MyAgent {

 init() {
        let notificationCenter = NSWorkspace.shared.notificationCenter

        // Register for system shutdown notification
        notificationCenter.addObserver(self,
                                       selector: #selector(handleNotification(_:)),
                                       name: NSWorkspace.willPowerOffNotification,
                                       object: nil)
RunLoop.current.run()
}

@objc func handleNotification(_ notification: Notification) {
        var logMessage = ""
        switch notification.name {
        case NSWorkspace.willPowerOffNotification:
            os_log("System is going to shut down at", log: log, type: .default)
            updatePlistFile(event: "shut down")
            let fileName = "example.txt"
            let content = "shut down"
            createAndWriteFile(fileName: fileName, content: content)
            logMessage = "System is going to shut down at \(Date())\n"
}
}

}

loaded the agent, and tried to restart device, I can't see as it is coming to handleNotification

Same code is working fine from sample application but not from launchAgent

Is there any restriction is there for NSWorkspace, if is that so, how to track shutdown/restart event from launchAgent or LaunchDaemon

Any help will be appreciate

Answered by DTS Engineer in 803292022

(getting back to this a bit late)

My larger goal here is at the lock screen I want to detect what events occured to reach this lock screen,

To do what? Some of what you're talking about here sounds like you might be better off modifying the login process more "directly", for example by using SFAuthorizationView.

Looking at you cases here:

restart or shutdown

There are two different issues here:

  1. Generally speaking, the larger "system" doesn't really differentiate between "restart" and "shutdown". Conceptually, a restart is basically just a shutdown which happens to be followed by an immediately scheduled startup. The system does have some "awareness" of it (basically for logging/diagnostic purposes), but we don't really want to create or encourage a situation where the startup sequence changes dramatically based on how the machine shutdown.

  2. The first login screen you see post-boot can be very, very complicated. On Intel mac, that screen is NOT part of "the booting system" but is actually an EFI "application". That dialog collects the login data, uses it to decrypt the boot data, then continues the boot process normally. The initial credentials received by EFI are passed into the kernel and then (eventually) used to complete the login process. The ARM process is different, but I believe there is a similar dynamic where the initial login process doesn't actually occur in the normal system context.

lock,?

I mentioned SFAuthorizationView above and this is why. The unlock screen you see over an account is the same screen/state as the login window. You can configure SFAuthorizationView to handle a variety of different states, including the login and lock screens, but the states themselves are completely different.

is it logout,

The typical reason developers are trying to "detect" this sort of event is because they want to do "something" when the user is logged in, in which case the right answer it to use a LaunchAgent which communicates with your daemon. TN2083: Daemons and Agents covers this in more detail.

Is there any kernel level

No. The important thing to keep in mind here is that the kernel isn't really involved with this "level" of the system. As far as it's concerned, it's job is to manage a basically "arbitrary" collection of resources (processes, threads, memory, mach ports, etc...). It doesn't really track any higher level concept (like "login"), particularly not in any kind of systemic way.

file or any other changes I can track from daemon to get to know this

No, not that I'm aware of.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

Track system event on macosx for shutdown and restart and update one plist with same event via launchAgent

The first question here is "what's your larger goal here". The issue here is that NSWorkspace (or anything in the agent context) isn't able to to ACTUALLY "know" that shutdown will actually occur. The issue here is that the shutdown process proceeds in a series of stages, but there are stages in that process where it could stall or be canceled.

Putting that in more concrete terms, the shutdown process goes something like this:

  1. Confirm that shutdown "should" occur with the user.
  2. Notify user processes ("agents"/apps) that this is going on and start quitting them.
  3. Finish destroying the user session, effectively "logging out".
  4. Start destroying the global session,
  5. Tell the kernel that shutdown should occur.
  6. Shutdown occurs.

Strictly speaking 4 & 5 are actually entangled with each other, however, the key issue here is that the kernel doesn't even KNOW that shutdown should occur until AFTER most/all LaunchAgents have already stopped running. That also means that shutdown can still be canceled well after your LaunchAgent has stopped running.

Moving to your specific code and "NSWorkspaceWillPowerOffNotification":

 init() {
        let notificationCenter = NSWorkspace.shared.notificationCenter

        // Register for system shutdown notification
        notificationCenter.addObserver(self,
                                       selector: #selector(handleNotification(_:)),
                                       name: NSWorkspace.willPowerOffNotification,
                                       object: nil)
RunLoop.current.run()
}

NSApplication is the "core" class inside AppKit and, inside it's class reference, it includes this warning:

"Many AppKit classes rely on the NSApplication class and may not work properly until this class is fully initialized."

In other words, there are classes and methods inside AppKit that simply will not function if NSApplication doesn't exist, which is exactly what's going on here. More specifically, NSApplication is what actually posts "NSWorkspaceWillPowerOffNotification", which it triggers based on receiving a "kAEQuitApplication" AppleEvent with a quite reason of "kAEQuitReason". As the names imply, those events do NOT in fact mean "the system is powering off". We set "kAEQuitReason" for user level logout/power off, but it's entirely possible for another process to set them. More to the point, the event is broadcast to "all" process and apps can cancel that process.

Agents are terminated after apps, so the notification is more reliable in an FBA than it would be in an app, however, it's sent for logout as well as power off, so you won't be able to differentiate the two. I also can't promise that a the power off won't be canceled by something in the global context.

That leads to your larger question:

how to track shutdown/restart event from launchAgent or LaunchDaemon

I don't have a good answer for that. By design, the lower level system avoids differentiating between "shutdown" and "normal termination", mostly because of two inherent paradoxes in this area:

  1. Processes attempting to detect logout/shutdown are attempting to execute code at EXACTLY the time the system is actively destroying the infrastructure (daemon's) our frameworks rely on to function.

  2. To varying degrees, it's possible to cancel these processes even after they're well underway, making any detection process inherently unreliable.

Note how these two factors interact- the more accurately you're able to "know" that the process will actually complete, the less your code is actually "able" to do. The kernel itself actually operates at the "end" of that issue. The IOKit power management system has a mechanism for delivering power events to user space and specific events for them (kIOMessageSystemWillPowerOff and kIOMessageSystemWillRestart), but specifically documents that they are never delivered to user space. The reason for that is quite simple- by the time the PM system starts this process, user space is not longer in a coherently functional state, assuming it hasn't been stopped entirely.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

Thanks @DTS Engineer Kevin Elliott for your reply

My larger goal here is at the lock screen I want to detect what events occured to reach this lock screen,

is it logout,lock,restart or shutdown?

Is there any kernel level file or any other changes I can track from daemon to get to know this

(getting back to this a bit late)

My larger goal here is at the lock screen I want to detect what events occured to reach this lock screen,

To do what? Some of what you're talking about here sounds like you might be better off modifying the login process more "directly", for example by using SFAuthorizationView.

Looking at you cases here:

restart or shutdown

There are two different issues here:

  1. Generally speaking, the larger "system" doesn't really differentiate between "restart" and "shutdown". Conceptually, a restart is basically just a shutdown which happens to be followed by an immediately scheduled startup. The system does have some "awareness" of it (basically for logging/diagnostic purposes), but we don't really want to create or encourage a situation where the startup sequence changes dramatically based on how the machine shutdown.

  2. The first login screen you see post-boot can be very, very complicated. On Intel mac, that screen is NOT part of "the booting system" but is actually an EFI "application". That dialog collects the login data, uses it to decrypt the boot data, then continues the boot process normally. The initial credentials received by EFI are passed into the kernel and then (eventually) used to complete the login process. The ARM process is different, but I believe there is a similar dynamic where the initial login process doesn't actually occur in the normal system context.

lock,?

I mentioned SFAuthorizationView above and this is why. The unlock screen you see over an account is the same screen/state as the login window. You can configure SFAuthorizationView to handle a variety of different states, including the login and lock screens, but the states themselves are completely different.

is it logout,

The typical reason developers are trying to "detect" this sort of event is because they want to do "something" when the user is logged in, in which case the right answer it to use a LaunchAgent which communicates with your daemon. TN2083: Daemons and Agents covers this in more detail.

Is there any kernel level

No. The important thing to keep in mind here is that the kernel isn't really involved with this "level" of the system. As far as it's concerned, it's job is to manage a basically "arbitrary" collection of resources (processes, threads, memory, mach ports, etc...). It doesn't really track any higher level concept (like "login"), particularly not in any kind of systemic way.

file or any other changes I can track from daemon to get to know this

No, not that I'm aware of.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

Track system event(shutdown/restart) via launchagent
 
 
Q