Showing posts with label system calls. Show all posts
Showing posts with label system calls. Show all posts

20100729

Installing a Helper Tool

If your application does something with/to files that the user wouldn't normally have access to, you can use IPC (inter-process communication) with an Elevated Helper Tool. But how do you install the Helper Tool?

Prior to OS X 10.6, you would have to use a Helper Tool Installer, which you would have to launch using AuthorizationExecuteWithPrivileges(...). The (authorized) Installer would also have to install a launchd plist and register the plist using launchctl.

On 10.6 "Snow Leopard", Apple has made this process easier with the Service Management APIs. There is one API now, SMJobBless, that takes your authorization and the name of the helper tool, and installs it. Here are some of the details of using it.

Both the application and the Helper Tool must be code signed. It isn't necessarily important who is the CA.

The Helper Tool must reside inside of your application bundle in the Contents/Library/LaunchServices directory. The name of the HelperTool executable file MUST be its launchd Label. This is usually a reverse-DNS label such as com.QuantumCheese.MyApp.HelperTool .

Furthermore, SMJobBless requires two custom sections in the __TEXT segment of your executable. The first of these is the info plist. This is a normal Info.plist file, customized for your Helper Tool. It must contain an array SMAuthorizedClients of code signing requirements for authorized clients. It is also a good idea to have a version string (the key is CFBundleVersion) and a bundle name (key CFBundleIdentifier) (probably the Label).
The second is the launchd plist. This is the same plist you would have written from the Installer executable; it ends up in /Library/LaunchDaemons. See man (5) launchd.plist for information on this file; it must include the Label, and it should not include Program or ProgramArguments keys (they will be ignored and replaced).

In the Xcode project settings for your helper tool's target, add Other Linker Flags of
-sectcreate __TEXT __info_plist /path/to/your/daemon's/info.plist
and
-sectcreate __TEXT __launchd_plist /path/to/your/daemon's/launchd.plist
Note that these two lines will be split into eight lines; make sure that each 4 stay together.

The documentation for SMJobBless says that the only domain currently supported is kSMDomainSystemLaunchd. Therefore, SMJobBless will take your Helper Tool, extract its __launchd_plist to /Library/LaunchDaemons/reverse-DNS-HelperTool.plist, and copy the Helper Tool to /Library/PrivilegedHelperTools/reverse-DNS-HelperTool. It automatically version checks, and the API returns true on successful upgrade or if the tool is already installed.

Experiments indicate that it will return true even if a newer version of the Helper Tool is installed. Therefore, if your IPC demands that your bundled version exactly matches the installed version, you will still need to use the Helper Tool Installer to downgrade.

Even though it is tempting, I recommend against putting an additional copy of the executable into the Contents/MacOSX folder. While doing so would let you use the CFBundleCopyAuxiliaryExecutableURL API to create a URL to the helper executable, it increases the size of your bundle, which is a bigger problem than finagling the helper executable's URL on 10.5.
Instead, I recommend appending the Contents/Library/LaunchServices/HelperTool path to the URL obtained from CFBundleCopyBundleURL(CFBundleGetMainbundle()).

20090715

launchd confusion

Does the following documentation indicate that one may not fork()/exec() in a launchd process? I'm certainly getting strange console messages when I do, but it's not clear yet if that's due to malformed code or if it's because of the way launchd treats children of children processes.

Citation: launchd documentation

Creating and running a launchd job

Whether you're migrating a job to launchd from some other system job launcher, or you're setting up a job for a new service, there are a few basic requirements for the job.

Jobs run from launchd should not duplicate launchd functionality; for instance, they should not use chroot(2). Furthermore, they should not do the things normally required of daemon processes, such as detaching from the terminal they are initially attached to. The only things that are strictly prohibited, however, are fork()/exit() combinations (including indirect methods, such as the daemon(3) library call). A server which attempts to run itself as a daemon in this way will seem to have finished running, potentially leading to launchd respawning it, or disabling the service. As launchd does not get stalled waiting for a child that hasn't yet exited, it's not necessary to try to prevent it.

20090714

CFFork

It is absolutely forbidden to use Core Foundation or higher-level APIs in a child process after fork()ing, before exec*()ing. This royally messes up our process model.

citation: OS X Leopard Core Foundation Release Notes

CoreFoundation and fork()
Due to the behavior of fork(), CoreFoundation cannot be used on the child-side of fork(). If you fork(), you must follow that with an exec*() call of some sort, and you should not use CoreFoundation APIs within the child, before the exec*(). The applies to all higher-level APIs which use CoreFoundation, and since you cannot know what those higher-level APIs are doing, and whether they are using CoreFoundation APIs, you should not use any higher-level APIs either. This includes use of the daemon() function.

Additionally, per POSIX, only async-cancel-safe functions are safe to use on the child side of fork(), so even use of lower-level libSystem/BSD/UNIX APIs should be kept to a minimum, and ideally to only async-cancel-safe functions.

This has always been true, and there have been notes made of this on various Cocoa developer mailling lists in the past. But CoreFoundation is taking some stronger measures now to "enforce" this limitation, so we thought it would be worthwhile to add a release note to call this out as well. A message is written to stderr when something uses API which is definitely known not to be safe in CoreFoundation after fork(). If file descriptor 2 has been closed, however, you will get no message or notice, which is too bad. We tried to make processes terminate in a very recognizable way, and did for a while and that was very handy, but backwards binary compatibility prevented us from doing so.

20090617

FSGetCatalogInfoBulk

I use this call to iterate over directory contents with fewer system calls. Sometimes, I delete files, and I need to open the iterator with kFSIterateDelete. The implementation is such that kFSIterateFlat was supposed to be able to permit such a delete-file operation, but does not in fact.
While testing the code, we commented out the actual delete, and found that the last n files in the directory were improperly processed. When we commented the delete back in, everything was cleared up.
The moral here is clear: When you open an FSIterator with the kFSIterateDelete flag, make sure to delete the files!