Trouble using DiskArbitration interface

"Merlijn B.W. Wajer" <[email protected]> Mon, 2 Jan 2017 17:08:05 +0100
Newsgroups gmane.comp.python.pyobjc.devel
Message-ID <[email protected]>
Hi,

I'm working on porting some CD archival software from Ubuntu to OS X,
and I am trying to prevent OS X from mounting CDs, because it interferes
with the CD ripping process.

I've found that I can register with 'diskarbitrationd' and register a
callback when a disk is about to be mounted: using
DiskArbitration.da.DARegisterDiskMountApprovalCallback

Using this interface, I've been able to block mount requests
successfully. However, after some time, usually after blocking a few
mounts, my simple python program segfaults, and I am pretty sure it has
something to do with memory management. I've tried to find information
about this on the official documentation, but that was a bit hard to
find, apart from "Do not worry about reference counting".

I am trying to re-create code from here, but in python with pyobjc:
https://developer.apple.com/library/content/documentation/DriversKernelHardware/Conceptual/DiskArbitrationProgGuide/ArbitrationBasics/ArbitrationBasics.html
(section "Approving or Refusing a Request")


Example [1] will always segfault, after (or while) returning from
allow_mount.
Example [2] is hacky, and works for a while, and will then segfault, but
confirms my suspicion that memory management issues are causing these
problems.

Specifically, I fear that the dissenter object that I create is somehow
being freed or corrupted. However, if create it only once for the
program lifetime, I get similar issues - so it seems like it is simply
being freed at some point?

Triggering OS X to mount can be done like so:

	diskutil mount /dev/disk1

if the mount is blocked, it will say:

	Volume on disk1 failed to mount; if it has a partitioning scheme, use
"diskutil mountDisk"
	If the volume is damaged, try the "readOnly" option

And unmounting can be done like so:

	diskutil unmount /Volumes/Audio\ CD/

Any suggestions on how to tackle or debug this problem are most welcome. :-)

Regards,
Merlijn



Example 1:

Output:

$ python osxdiskarbitration-example.py
OSXDA: start_blocking started
OSXDA: Session: <DASession 0x7fc9a9fa2ee0 [0x1059e9ed0]>{id = python
[3595]:7947}
OSXDA: allow_mount call:  <DADisk 0x7fc9ab800600 [0x1059e9ed0]>{id =
/dev/disk1} 0
OSXDA: Blocking mount


Code:

from __future__ import print_function
import objc
objc.options.verbose = True
import DiskArbitration as da

@objc.callbackFor(da.DARegisterDiskMountApprovalCallback)
def allow_mount(disk, context):
    print('OSXDA: allow_mount call: ', disk, context)

    if True:
        #dissenter = da.DADissenterCreate(da.kCFAllocatorDefault,
        #                                  da.kDAReturnExclusiveAccess,
None)

        # We cannot pass kDAReturnExclusiveAccess initially, for some
reason, this raises:
        # ValueError: depythonifying 'int', got 'int' of wrong magnitude
        # So set it to 42, and change it later
        dissenter = da.DADissenterCreate(da.kCFAllocatorDefault, 42, None)
        dissenter["DAStatus"] = da.kDAReturnExclusiveAccess

        print('OSXDA: Blocking mount')
        return dissenter # Deny
    else:
        print('OSXDA: Allowing mount')
        return None # Allow

def start_blocking():
    print('OSXDA: start_blocking started')
    session = da.DASessionCreate(da.kCFAllocatorDefault)

    if not session:
        print('OSXDA: Failed to create session')
        exit(1)

    print('OSXDA: Session:', session)

    da.DASessionScheduleWithRunLoop(session, da.CFRunLoopGetMain(),
                                    da.kCFRunLoopCommonModes);
    #da.DASessionScheduleWithRunLoop(session, da.CFRunLoopGetCurrent(),
    #                                da.kCFRunLoopDefaultMode);

    # Register callback
    da.DARegisterDiskMountApprovalCallback(session, None, allow_mount, None)

    da.CFRunLoopRun()

    da.DASessionUnscheduleWithRunLoop(session, da.CFRunLoopGetCurrent(),
da.kCFRunLoopDefaultMode);

    print('OSXDA: Done with loop')

    CFRelease(session)

if __name__ == '__main__':
    start_blocking()


Example 2:

Output:

$ python osxdiskarbitration-example-2.py
OSXDA: start_blocking started
OSXDA: Session: <DASession 0x7f8e9bf56cf0 [0x1022aced0]>{id = python
[3644]:7947}
OSXDA: allow_mount call:  <DADisk 0x7f8e9be73340 [0x1022aced0]>{id =
/dev/disk1} 0
OSXDA: Blocking mount
OSXDA: allow_mount call:  <DADisk 0x7f8e9bd052e0 [0x1022aced0]>{id =
/dev/disk1} 0
OSXDA: Blocking mount
OSXDA: allow_mount call:  <DADisk 0x7f8e9be73340 [0x1022aced0]>{id =
/dev/disk1} 0
OSXDA: Blocking mount
OSXDA: allow_mount call:  <DADisk 0x7f8e9be73340 [0x1022aced0]>{id =
/dev/disk1} 0
OSXDA: Blocking mount
OSXDA: allow_mount call:  <DADisk 0x7f8e9be73b80 [0x1022aced0]>{id =
/dev/disk1} 0
OSXDA: Blocking mount
OSXDA: allow_mount call:  <DADisk 0x7f8e9be75240 [0x1022aced0]>{id =
/dev/disk1} 0
Segmentation fault: 11

Code:

from __future__ import print_function
import objc
objc.options.verbose = True
import DiskArbitration as da

memhack = []

@objc.callbackFor(da.DARegisterDiskMountApprovalCallback)
def allow_mount(disk, context):
    print('OSXDA: allow_mount call: ', disk, context)

    if True:
        #dissenter = da.DADissenterCreate(da.kCFAllocatorDefault,
        #                                  da.kDAReturnExclusiveAccess,
None)

        # We cannot pass kDAReturnExclusiveAccess initially, for some
reason, this raises:
        # ValueError: depythonifying 'int', got 'int' of wrong magnitude
        # So set it to 42, and change it later
        dissenter = da.DADissenterCreate(da.kCFAllocatorDefault, 42, None)
        dissenter["DAStatus"] = da.kDAReturnExclusiveAccess
        memhack.append(dissenter)

        print('OSXDA: Blocking mount')
        return dissenter # Deny
    else:
        print('OSXDA: Allowing mount')
        return None # Allow

def start_blocking():
    print('OSXDA: start_blocking started')
    session = da.DASessionCreate(da.kCFAllocatorDefault)

    if not session:
        print('OSXDA: Failed to create session')
        exit(1)

    print('OSXDA: Session:', session)

    da.DASessionScheduleWithRunLoop(session, da.CFRunLoopGetMain(),
                                    da.kCFRunLoopCommonModes);
    #da.DASessionScheduleWithRunLoop(session, da.CFRunLoopGetCurrent(),
    #                                da.kCFRunLoopDefaultMode);

    # Register callback
    da.DARegisterDiskMountApprovalCallback(session, None, allow_mount, None)

    da.CFRunLoopRun()

    da.DASessionUnscheduleWithRunLoop(session, da.CFRunLoopGetCurrent(),
da.kCFRunLoopDefaultMode);

    print('OSXDA: Done with loop')

    CFRelease(session)

if __name__ == '__main__':
    start_blocking()

------------------------------------------------------------------------------
Check out the vibrant tech community on one of the world's most 
engaging tech sites, SlashDot.org! http://sdm.link/slashdot