How to daemonize non-privileged script in OSX Yosemite 10.10.3+ - macos

For years we've had a process-monitoring/control script as part of our application. The default behavior of the script is to daemonize itself. Often the script is launched, of necessity, by non-privileged users. For reasons I'll not elaborate, we need to keep both the script and this behavior.
On OSX systems, we have traditionally had the script restart itself in the background via the /usr/libexec/StartupItemContext launch script provided by Apple. This puts our process in the Mach StartupItem bootstrap context rather than the login bootstrap context. This is necessary because without that context switch, if and when the user logs out, which is also often necessary, the script loses access to directory services, getpwuid(), DNS services, etc. The original internal lines that daemonized the script looked essentially like this (in perl):
my $cmd = "/usr/libexec/StartupItemContext myscript #Commandline > logs/startup 2>&1" ;
system( "$cmd &") ;
exit 0 ;
When OSX Yosemite came out, that StartupItemContext script disappeared, so we switched to direct invocation of launchctl:
my $cmd = "/usr/launchctl bsexec / myscript #Commandline > logs/startup 2>&1" ;
system( "$cmd &") ;
exit 0 ;
With the recent OSX 10.10.3 upgrade, however, the bsexec subcommand of launchctl suddenly requires root privileges:
% launchctl bsexec
This subcommand requires root privileges: bsexec
%
This creates for us the showstopper problem that non-privileged users can no longer get our monitoring/control script to daemonize itself.
It seems that Glassfish has encountered this problem and addressed it with a patch that replaces
/bin/launchctl bsexec /
with
nohup
This may work for the Glassfish implementation, however I don't think for us. Notwithstanding the fact that I don't understand it -- i.e. why a simple blocking of SIGHUP would prevent a process in decommissioned login bootstrap context from losing services -- it also doesn't seem to work in our tests for all system services we need.
What is the new, canonical way to daemonize a process on OSX starting from a non-privileged, Mach "login" bootstrap context, without losing access to critical system services like DNS etc. when the user logs out?

"from a non-privileged, Mach "login" bootstrap context" unfortunately is very unlikely to have a "canonical way." The only canonical way is to launch services on demand via launchd. Even "bsexec" is barely supported and hardly documented. In my experience, it isn't possible to keep up with OS X changes and never redesign your launch systems. I redesign the daemon system about every other release because Apple breaks it, and they're going to keep breaking it. The only answer is to keep trying to make your requirements simpler. But pretty much any process that wants to run all the time, rather than on demand, is in direct violation of Apple's stated intent, so it's going to tend to break.
The solution given by Apple is to create a LaunchDaemon and assign it a UserName in your launchd plist. You must start privileged, and then switch to the user (and it needs to be a fixed user, not "the logged in user" since that would be a LaunchAgent). You can't upgrade your access this way. You must not daemonize yourself (again, the canonical answer is: don't do that. See the launchd.plist man page.)
I suspect the Glassfish nohup solution is just a dodge that doesn't actually put them in the Mach context. They're just trying to avoid being killed when the parent shell exits. That probably won't help you.
In my experience, the most robust solutions are multi-part. You wind up with one piece that is run as a system LaunchDaemon (with KeepAlive), and another piece that is a user LaunchAgent, and you let them communicate over IPC so that you can access the context you need for each activity you need to do. Yes, this is typically much more complicated to implement. Simpler solutions tend not to work.
And of course you have to constantly ask yourself "is there any way I could achieve this by doing things the way Apple wants me to." That means XPC is strongly preferred, followed by on-demand LaunchDaemons and LaunchAgents. If you build your system to use XPC components, you'll likely survive more OS X upgrades. Any work around you figure out that doesn't use these pieces will probably have to be fixed again in 10.11.

Related

In which of these scenarios will my crontabs run, when won't they?

Beginner here. I have a few questions regarding crontabs, on mac / osx (if it makes a difference). I'd like to understand in what scenarios my crontab will run, and when it doesn't.
I do know that this is true:
When I turn off my computer, the cronjob won't be executed.
That was the easy part. But what about these cases:
The terminal is not open, but I am logged in
The machine is running, but I am not logged in
The cronjob is set for 9am, but my computer is off at the time, and I turn it on & log in at 10am
When will it work, when won't it?
cron jobs run while the computer is on & not sleeping, so it will run in situations 1 and 2. If the computer is off or asleep at the job's scheduled time, it does not do any sort of catch-up run later when the computer restarts/wakes up; therefore, it will not run in situation 3.
Let me also clarify about situations 1 and 2: cron jobs run independently of any user login sessions, and any programs they're running. They can't read from Terminal input, anything they print won't show up on screen, and since they aren't part of your login session they have a limited ability to interact with the regular graphical user interface and running programs. They live in a semi-separate world from the programs (including Terminal commands) you run interactively.
Note that crontabs are generally deprecated on macOS; the preferred way to run programs automatically is with launchd. But the launchd equivalent of a user crontab, called a Launch Agent, does run as part of a logged-in user session (and -- mostly -- gets skipped when the user is not logged in). And also, launchd jobs do get run if a scheduled run gets missed because the computer is off/sleeping. So they're quite a bit different from cron jobs.

Handling SIGABRT in Mac Apps

I have created a very simple Desktop app for Mac OS. Due to restrictions, I could not use xcode. I am building the .app directory by hand, and the main executable at the moment is a shell script.
When I was trying my app out, I noticed that if I opened and closed it too quickly, the app would freeze up. At which point, I seemed unable to even force quit it, and had to rm -r the .app itself. A friend mentioned to me, that mac apps must handle SIGABRTs, and if they do not, there is a timeout period where the app could appear as frozen, which might explain what I observed.
I was looking around but uncertain where to find more information about this. Can anyone further explain this situation? Under what circumstances will the app receive a SIGABRT, and how should it be handled? Any links or literature on this topic, would be very appreciated.
In case anyone ever stumbles upon this:
So my friend was referring to Unix signals here. https://people.cs.pitt.edu/~alanjawi/cs449/code/shell/UnixSignals.htm
(to see what is available on your OS, give 'kill -l')
My main executable, in my MyApp.app/Contents/MacOS, is a shell script. So what I've found I can do, is use trap command. This will give a behavior to perform, if the executable receives one of the signals. Example - I now add this line near the top of my shell script:
trap 'exit' 5 6
This means that if the executable receives with a SIGABRT (6) or a SIGTRAP (5) signal, it will perform the command 'exit' and will exit. (I am not certain which all signals should be handled and what is the best course, I guess that might depend on your own app, but that was just as an example of something to do)
Here is a resource about trap commands and unix signals: https://www.tutorialspoint.com/unix/unix-signals-traps.htm
Why does this make a difference - I believe previously, there were scenarios where for example if I opened the app while it was already open, it was receiving a Unix signal like a SIGABRT. This signal was not being handled and the app did not know what to do in that scenario, and was freezing up. Though I have not confirmed this is what was happening.

How to call system open from bash script

I've hooked the system call to typedef int (*orig_open_f_type)(const char *__file, int __oflag, ...); and thus, whenever a file gets opened, my code gets the event before it is passed on to the system. I created a dynamic library that overrides the open call and inject this library using DYLD_INSERT_LIBRARIES - working on a Mac machine and using XCode. It is a standard step that enables me to hook calls.
Now, I have bash script in which I have some files that I want to open. I have tried xdg-open , cat, exec - but they are not triggering the system call to open the file.
How should I invoke this open call in my bash script?
Please note that I have tested my open call hook, by opening files in C code.
I believe you're running foul of Apple's SIP (System Integrity Protection) which is designed to stop people doing things like that with system-provided executables. SIP was added to Mac OS X El Capitan (10.11) and continues in macOS Sierra (10.12).
To demonstrate whether this is the problem, consider copying /bin/cat to /usr/local/bin/cat and then try hooking (running) the local copy. You might get away with it there. This 'workaround' is purely for demonstration purposes. Basically, if I'm right, SIP is Apple's way of saying "don't go messing with our software".
You can follow links from Can Mac OS X El Capitan run software compiled for Yosemite that expects libraries in /usr/gnu/lib? to find out more about SIP. Following links via What is the "rootless" feature in El Capitan, really? on Ask Different to a blog article on System Integrity Protection, it says explicitly:
Runtime protection
SIP’s protections are not limited to protecting the system from filesystem changes. There are also system calls which are now restricted in their functionality.
task_for_pid() / processor_set_tasks() fail with EPERM
Mach special ports are reset on exec(2)
dyld environment variables are ignored
DTrace probes unavailable
However, SIP does not block inspection by the developer of their own applications while they’re being developed. Xcode’s tools will continue to allow apps to be inspected and debugged during the development process.
For more details on this, I recommend taking a look at Apple’s developer documentation for SIP.
Emphasis added
Basically, this means that you won't be able to hook calls to the open() system call for Apple-supplied software installed in the system directories. You will need to rethink what you are trying to do.
Running any normal command -- like cat -- that processes a file will cause the file to be opened. You can also open a file (and immediately close it) using the shell syntax:
: < /path/to/file
If your system call hook isn't getting called, something must be wrong with your hook -- there's no way these commands are working without opening the file. Alas, you haven't explained how you implemented your hook, so we have no way of debugging that.
The file command opens the file to look at its contents.
$ file /path/to/file
I have suggested this because it eventually leads to having the system call open which can be confirmed using strace.
$ strace file /path/to/file 2>&1 | grep open
I thought one of the good things about using file is that it opens the file in read only mode. In comparison to other ideas, unlike cat, it will not have to run through the entire file, just part of it, so the time complexity using file may be constant. Unlike vim, which someone has suggested, file will return when finished and not block like a text editor would.

Custom shell script to kill uWSGI by pid

I'm to write a script which will grab all the text from a file (/tmp/pidfile.txt) which is just a pid number, and then store that as a variable (say pidvar) or something and execute the following:
kill -2 pidvar
Seems simple enough I just don't know how to grab the pid from the .txt file. I have python installed if this helps. Trying to make it easier to kill uWSGI, any suggestions on an alternative would be welcome.
Thanks in advance for any help.
The literal answer to your question (using a bash extension to be slightly more efficient) would be
kill -2 "$(</tmp/pidfile.txt)"
...or, to be compatible with POSIX sh but slightly less efficient...
kill -2 "$(cat /tmp/pidfile.txt)"
...but don't do it either of those ways.
pidfiles are prone to race conditions, whereas process-tree-based supervision systems can guarantee that they only ever deliver a signal to the correct process.
runit, daemontools, Upstart, systemd, and many other alternatives are available which will guarantee that there's no risk of sending a signal to the wrong process based on stale data. CentOS is probably the last major operating system that doesn't ship with one of these (though future versions will almost certainly use systemd), but they're available as third-party packages -- and if you want your system to be reliable (detecting unexpected failures and restarting services as soon as they go down, for instance, without having to do it with your own code), you should be using one of them.
For instance, with systemd:
systemctl kill -s SIGINT uwsgi.service
...or, with runit:
sv interrupt uwsgi
...whereas with upstart, you can configure a completely arbitrary restart command to be triggered on initctl reload uwsgi.
For general best-practices documentation on using shell scripts for process management, see the appropriate page on the wooledge.org wiki, maintained by irc.freenode.org's #bash channel.
It is generally easier to ask uwsgi to kill itself. You can do this by using the "master-fifo" option in your config, and then send a "q" to the fifo. This is described here: http://uwsgi-docs.readthedocs.org/en/latest/MasterFIFO.html.

fork within Cocoa application

My problem is not the best scenario for fork(). However, this is the best func I can get.
I am working on a Firefox plugin on Mac OSX. To make it robust, I need to create a new process to run my plugin. The problem is, when I forked a new process, much like this:
if (fork() == 0) exit(other_main());
However, since the state is not cleaned, I cannot properly initialized my new process (call NSApplicationLoad etc.). Any ideas? BTW, I certainly don't want create a new binary and exec it.
In general, you need to exec() after fork() on Mac OS X.
From the fork(2) man page:
There are limits to what you can do in the child process. To be totally safe you should restrict your-self to only executing async-signal safe operations until such time as one of the exec functions is called. All APIs, including global data symbols, in any framework or library should be assumed to be unsafe after a fork() unless explicitly documented to be safe or async-signal safe. If you need to use these frameworks in the child process, you must exec. In this situation it is reasonable to exec yourself.
TN2083 also comments on this subject:
Many Mac OS X frameworks do not work reliably if you call fork but do not call exec. The only exception is the System framework and, even there, the POSIX standard places severe constraints on what you can do between a fork and an exec.
IMPORTANT: In fact, in Mac OS X 10.5 and later, Core Foundation will detect this situation and print the warning message shown in Listing 13.
Listing 13: Core Foundation complaining about fork-without-exec
The process has forked and you cannot use this CoreFoundation functionality safely. You MUST exec().
Break on __THE_PROCESS_HAS_FORKED_AND_YOU_CANNOT_USE_THIS_COREFOUNDATION_FUNCTIONALITY___YOU_MUST_EXEC__() to debug.
fork without exec is basically entirely unsafe on OSX. You will end up with stale mach ports for example.
I'm writing the FreeWRL plugin for Firefox (Linux at the moment, Mac & Windows soon).
http://freewrl.sourceforge.net/
It's based on fork+exec to launch FreeWRL and swallow its window into Firefox.
You'll have to use a pipe to correctly handle the possible failure of fork+exec or the failure of your child process :
How to handle execvp(...) errors after fork()?
Cheers,
C

Resources