Wednesday, April 29, 2015

Disabling OS-X Device Removal Warnings In Yosemite

If you're anything like me you've probably seen the Disk Not Ejected Properly message so many times that you've forgotten how annoying it is.. despite the fact that you never even wrote anything to the usb drive that you're yanking out of your Mac the operating system insists that you remove it "correctly". You're supposed to click the eject button. Why? I dunno, Apple seems to think that if you can write to it then the operating system might have written to it - and being Apple, they probably have. Well ya know what? Fuck you, I won't do what you tell me.

Note: this will not fix your external drive disconnecting when your Mac goes to sleep. For that problem try disabling "put hard drive to sleep when possible" in both the Battery and Power Adapter tabs of the OS energy saver settings. What appears below is a dirty hack for deliberately removing a warning that Apple insists on showing you every single time you yank out a usb stick.

The short version. WARNING, this may break your Mac! Be careful! Download my hacked DiskArbitrationAgent. Open a terminal and enter the following commands:

cd /System/Library/Frameworks/DiskArbitration.framework/Versions/A/Support
sudo cp DiskArbitrationAgent DiskArbitrationAgent.original
password: [Enter your password]
sudo cp ~/Downloads/DiskArbitrationAgent .
killall DiskArbitrationAgent

The long version. What does this do? Read on.

In my quest to remove this annoying and pointless warning, I started with a Google search and turned up various answers of the form "don't do that" and guides on how to press the eject button. Gee, thanks! One more helpful answer suggested disabling the User Notification Center - which is akin to cutting off your nose to spite your face. Okay, so I get that Apple doesn't want me to turn off this warning, but I don't care what they think. I'm turning it off.

My next course of action was bargaining. I figured the engineers at Apple are pretty reasonable, they wouldn't throw this annoying warning in my face if I mounted the drive read-only, surely? Using sudo vifs I added a line to my /etc/fstab with an appropriate UUID. After inserting the usb drive I ran mount to ensure it was mounted read-only, it was. I then proceeded to yank out the drive without pressing Eject and still got the cursed warning! Apple engineers are not reasonable.

In fact, I actually tracked down the code that does the appropriate check. It's in DARequest.c in diskarbitrationd and it looks like this:

if ( DADiskGetState( disk, kDADiskStateZombie ) )
{
    DARequestSetState( request, kDARequestStateStagedApprove, TRUE );

    if ( DADiskGetDescription( disk, kDADiskDescriptionMediaWritableKey ) 
             == kCFBooleanTrue )
    {
        DADialogShowDeviceRemoval( disk );
    }
}

Hey idiots, MediaWritable means the media is writable, not that the drive is mounted read-write. You're displaying a warning for a potential problem that is impossible. Way to go!

So where does this warning come from? It's displayed as a result of that DADialogShowDeviceRemoval() call, but that's just a message sending wrapper. I found out. The offending code is in DADialog.m in DiskArbitrationAgent and it looks like this:

static NSString * __kDADialogLocalizedStringDeviceRemovalKey      = 
    @"Eject \"%@\" before disconnecting or turning it off.";
static NSString * __kDADialogLocalizedStringDeviceRemovalTitleKey = 
    @"Disk Not Ejected Properly";

...

void DADialogShowDeviceRemoval( DADiskRef disk )
{
    NSUserNotificationCenter * center;

    center = [ NSUserNotificationCenter _centerForIdentifier: 
                   @_kDAAgentName type: 
                   _NSUserNotificationCenterTypeSystem ];

    if ( center )
    {
        NSUserNotification * notification;

        notification = [ [ NSUserNotification alloc ] init ];

        if ( notification )
        {
            NSBundle * bundle;

            bundle = [ NSBundle bundleWithPath: 
                       __kDADialogLocalizedStringBundlePath ];

            if ( bundle )
            {
                NSDictionary * description;

                description = ( __bridge_transfer id ) 
                    DADiskCopyDescription( disk );

                if ( description )
                {
                    NSString * name;

                    name = [ description objectForKey: ( __bridge id ) 
                        kDADiskDescriptionVolumeNameKey ];

                    if ( name == NULL )
                    {
                        name = __DALocalizedStringInBundle( @"Untitled", 
                            bundle );
                    }

                    notification.hasActionButton = FALSE;
                    notification.informativeText = [ NSString 
                        stringWithFormat: __DALocalizedStringInBundle(
                        __kDADialogLocalizedStringDeviceRemovalKey, 
                        bundle ), name ];
                    notification.title = __DALocalizedStringInBundle(
                        __kDADialogLocalizedStringDeviceRemovalTitleKey, 
                        bundle );
                    notification._imageURL       = [ NSURL 
                        fileURLWithPath: 
                        @"/System/Library/CoreServices/CoreTypes.bundle
                        /Contents/Resources/FinderIcon.icns" ];
                    notification._persistent     = FALSE;

                    [ center deliverNotification: notification ];
                }
            }
        }
    }
}

You can find the DiskArbitrationAgent in /System/Library/Frameworks/DiskArbitration.framework/Versions/A/Support and on my Mac it's 22992 bytes long. Using otool -tV we see the offending code:

0000000100000faf        pushq   %rbp
0000000100000fb0        movq    %rsp, %rbp
0000000100000fb3        pushq   %r15
0000000100000fb5        pushq   %r14
0000000100000fb7        pushq   %r13
0000000100000fb9        pushq   %r12
0000000100000fbb        pushq   %rbx
0000000100000fbc        subq    $0x68, %rsp
0000000100000fc0        movq    %rdi, %r14
0000000100000fc3        movq    0x1556(%rip), %rdi  ## Objc class ref: 
                                _OBJC_CLASS_$_NSUserNotificationCenter
0000000100000fca        movq    0x14b7(%rip), %rsi  ## Objc selector ref: 
                                            _centerForIdentifier:type:
0000000100000fd1        leaq    0x13a8(%rip), %rdx  ## Objc cfstring ref:
                     @"com.apple.DiskArbitration.DiskArbitrationAgent"
0000000100000fd8        movl    $0x2, %ecx
0000000100000fdd        callq   *0x10b5(%rip)       ## Objc message:
                +[NSUserNotificationCenter _centerForIdentifier:type:]
0000000100000fe3        movq    %rax, %rdi
0000000100000fe6        callq   0x1000019e6         ## symbol stub for:
                                   _objc_retainAutoreleasedReturnValue
0000000100000feb        movq    %rax, %rbx
0000000100000fee        testq   %rbx, %rbx
...
00000001000012de        leaq    0xf7b(%rip), %rdx   ## Objc cfstring ref:
                 @"Eject "%@" before disconnecting or turning it off."
...

If we hexdump -C DiskArbitrationAgent we can see the actual bytes too:

00000fa0  48 83 c4 08 5b 41 5c 41  5d 41 5e 41 5f 5d c3 55  |H...[A\A]A^A_].U|
00000fb0  48 89 e5 41 57 41 56 41  55 41 54 53 48 83 ec 68  |H..AWAVAUATSH..h|

Yes, the text segment of Mach-O images nicely correspond in memory as they do on disk - at least in non-fat binaries. Okay, that's boring to just about everyone except me. How do we get rid of this warning? That's easy, all we have to do is insert a c3 at faf. You can use a hex editor, but I just wrote this tiny program:

#include <stdio.h>

int main()
{
 FILE *f = fopen("DiskArbitrationAgent", "r+");
 fseek(f, 0xfaf, SEEK_SET);
 unsigned char r = 0xc3;
 fwrite(&r, 1, 1, f);
 fclose(f);
 return 0;
}

I recommend making a backup of the original file before fiddling with it. We then need to killall DiskArbitrationAgent and we're done. Too hard? Here's my hacked DiskArbitrationAgent.

I hoped you have enjoyed this exercise and don't trash your Mac. :)