In a precedent article I was showing you how to prevent a disk from mounting on the file system, at the end of the artcile I said that it would probably be best if this kind of program could run as a daemon, so this is what I’ll show you in this post, to begin let’s take a look at the precedent code :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| #import <Foundation/Foundation.h>
#import <DiskArbitration/DiskArbitration.h>
DADissenterRef diskDidMount(DADiskRef dsk, void* context)
{
DADissenterRef ref = DADissenterCreate(kCFAllocatorDefault, kDAReturnNotPermitted, NULL);
return ref;
}
int main(int argc, const char* argv[])
{
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
DASessionRef session = DASessionCreate(kCFAllocatorDefault);
DARegisterDiskMountApprovalCallback(session, NULL, diskDidMount, NULL);
DASessionScheduleWithRunLoop(session, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
CFRunLoopRun();
[pool drain];
return 0;
} |
First, there are some things that you need to know, OS X is an UNIX system but since Mac OS X 10.4 the daemons aren’t handled like the traditionals *BSD systems.
Since this release, daemons are managed by launchd. OS X have 2 kind of programs that run in background :
- Daemons : Their owner is the system, there are launched even when nobody is logged and a very important thing, they don’t have to launch any kind of GUI.
- Agents : Their owner is a normal user, there are only launched when an user is logged and can display a GUI.
In order to make a difference betwneen them, there are 2 distincts folders : /Library/LaunchDaemons and /Library/LaunchAgents
So let’s get working now, there is noting hard, first we will take a look at the Apple doc, here and here.
The second link is important because it shows what frameworks are daemons safe, and you can see that DiskArbitration is daemon safe, so we can adapt our program to run as a daemon.
Now look at the first link, it’s the man for launchd.plist, the first section that you must read is the EXPECTATIONS one, it tells you what you can do, what you can’t and what you must.
If you read correctly, you know that you need 2 fields at least in your plist :
- Label
- Program or ProgramArguments
You can take a look to the others flags too, some are interesting, like RunAtLoad which indicates that the program will run at login.
Here is our finalized plist :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>fr.whine.diskblocker</string>
<key>RunAtLoad</key>
<true/>
<key>Program</key>
<string>/tmp/DiskBlocker</string>
<key>UserName</key>
<string>Nyxouf</string>
</dict>
</plist> |
The name of the file must follow this scheme : [Label].plist, in my case I name it fr.whine.diskblocker.plist.
Let’s put the file in /Library/LaunchDaemons and you are done with the config file.
Now we need to add some code in our program to adapt it to run as a daemon. If you correctly read the doc, you know that you must catch the SIGTERM signal in order to terminate properly the daemon (free the memory etc..)
There is nothing complicated, to do that we use the functions in signal.h, we first create an handler function :
1
2
3
4
5
6
7
| void SIGTERM_handler(const int sigid)
{
if (SIGTERM == sigid)
{
CFRunLoopStop(CFRunLoopGetCurrent());
}
} |
In the main function before the runloop, we call the signal function :
1
| signal(SIGTERM, (sig_t)SIGTERM_handler); |
Once the signal received, the runloop will stop, so we could properly end the program :
1
2
3
4
| DASessionUnscheduleFromRunLoop(session, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
CFRelease(session);
[pool drain];
return 0; |
Now, let’s compile
1
| gcc DiskBlocker.m -o /tmp/DiskBlocker -framework DiskArbitration -framework Foundation |
In the man, it’s written that the system daemon owner must be root, so need to do a chown :
1
| sudo chown root:wheel /tmp/DiskBlocker |
Last thing, launch the daemon, to do that we use launchctl
1
| launchctl load /Library/LaunchDaemons/fr.whine.diskblocker.plist |
If like me, you specified the RunAtLoad flag, you can try to reboot to se if the daemon is launched
You can get all the source code here
see ya!