Skip to content

Select the drive that was actually requested on macOS#330

Merged
saramibreak merged 1 commit into
saramibreak:masterfrom
gmipf:fix/macos-drive-selection
Jul 20, 2026
Merged

Select the drive that was actually requested on macOS#330
saramibreak merged 1 commit into
saramibreak:masterfrom
gmipf:fix/macos-drive-selection

Conversation

@gmipf

@gmipf gmipf commented Jul 12, 2026

Copy link
Copy Markdown
Contributor

On macOS, GetSCSITaskInterface() (_linux/defineForLinux.cpp) derives the BSD name from the drive argument but never compares it against anything. The loop takes the first SCSITaskAuthoringDevice IOKit hands out and breaks; the name is then used only for the diskutil unmountDisk call further down. DiscImageCreator unmounts the drive that was asked for and opens whichever drive happened to be enumerated first.

The other platforms cannot do this. GetHandle() opens the named device directly - CreateFile("\\\\.\\%c:") on Windows, open(pDevice->drivepath) on Linux - so the drive that was named is the drive that is used, and when it cannot be used the run fails on that drive. Only the macOS path enumerates, and it enumerates without looking at the name.

The argument is unused, not merely mismatched

A drive name that exists nowhere on the machine:

Linux   $ DiscImageCreator cd /dev/sr9 out.bin 8
        [F:GetHandle][L:63] GetLastError: 2, No such file or directory

macOS   $ DiscImageCreator cd disk99 out.bin 8
        Class: IODVDServices
        Path: IOService:/AppleACPIPlatformExpert/PCI0/AppleACPIPCI/SEF@1D,7/SEF@fd000000/
              AppleUSBEHCIPort@fd100000/External USB Storage Device@fd100000/...
        unmountCmd: diskutil unmountDisk disk99 > /dev/null

Linux fails on the drive it was given. macOS opens a different one - here the USB Plextor.

What that costs

The test machine has two optical drives:

BSD name drive disc SCSITaskAuthoringDevice
disk1 QEMU DVD-ROM (emulated) ISO MPFTEST, 8.8 MB, one data track no
disk4 PLEXTOR DVDR PX-760A (USB) PhotoCD Portfolio, 310.6 MB, data + audio track yes

Asking for disk1 on current master unmounts disk1, opens the Plextor, and then runs with no error and no warning. The files named after disk1 describe a disc that is not in disk1:

asked-for-disk1_drive.txt:   VendorId: PLEXTOR
                             ProductId: DVDR   PX-760A
                             ProductRevisionLevel: 1.07

asked-for-disk1.toc:          Data Track  1, LBA     0 -  28051, Length  28052
                             Audio Track  2, LBA 28052 - 132072, Length 104021

That is the mixed-mode PhotoCD sitting in disk4. The disc in disk1 is a single 8.8 MB data track.

When the wrongly opened drive still holds a mounted volume the failure is loud but misleading instead: ObtainExclusiveAccess returns e00002d5 (kIOReturnBusy) and the run aborts with GetLastError: 2, No such file or directory, because the unmount went to the drive that was not opened. A disc macOS cannot mount - audio, console, damaged - is never mounted, so in that case nothing stops the wrong dump.

This does not make an unsupported drive dumpable

disk1 is an emulated drive that macOS does not publish as a SCSITaskAuthoringDevice. It cannot be dumped, and it should not be. The problem is what happens instead of saying so. After this change DiscImageCreator does say so, and shows what is there:

$ DiscImageCreator start disk1
Not found the drive: disk1
Available drive:
        disk4     PLEXTOR DVDR   PX-760A

Which drives are supported and how they are dumped is untouched. Only the choice of which IOKit service gets opened changes.

Change

Collect the authoring devices and pick the requested one out of them, by BSD name when a disc is loaded and by drive name otherwise.

The BSD name is not a property of the SCSI peripheral device. It belongs to the IOMedia object that the block storage stack publishes below it, so it is searched for with IORegistryEntrySearchCFProperty() and kIORegistryIterateRecursively rather than read from the device node. This is the same lookup redumper uses on macOS.

macOS assigns that name only while a disc is loaded, so it cannot address an empty drive - and eject, close, start and stop need exactly that. Vendor and product from the device characteristics are there with or without a disc, so they serve as the name in that case (PX-760A, PLEXTOR, case insensitive). A name that fits more than one drive is refused rather than guessed.

diskutil unmountDisk now runs on the drive that is about to be opened rather than on the string that was passed in, and is skipped when that drive holds no disc.

Before / after

requested before after
disk1 opens disk4 (Plextor) Not found the drive: disk1, lists disk4
disk99 opens disk4 (Plextor) Not found the drive: disk99, lists disk4
disk4 opens disk4 opens disk4, dump runs (VendorId: PLEXTOR, TOC and disc info read)
PX-760A opens disk4 by luck opens disk4
plextor opens disk4 by luck opens disk4

With the disc ejected, so the drive has no BSD name at all:

requested after
disk4 Not found the drive: disk4, lists (no disc) PLEXTOR DVDR PX-760A
PX-760A opens the drive, close closes the tray

Testing

macOS 26.5.2, x86_64, built with meson/ninja and Homebrew libarchive + openssl@3. Real USB PLEXTOR PX-760A with a mixed-mode disc, plus a second optical drive that is not an authoring device. Every row of both tables above was run on that machine, and a full cd dump still reads the disc through the new selection.

The machine has only one authoring device, so the case of two of them - where the first one enumerated wins and the second is unreachable - is not something I was able to run. What is shown above is the same defect from the other side: the name is never compared, so any name lands on the same drive.

Compiler warnings are unchanged (71 before, 71 after, -Wall -Wextra). The patched tree also builds on Linux (Fedora, 0 warnings); only the __APPLE__ branch is touched, so Linux and Windows are unaffected.


Prepared with AI assistance (Claude Opus 4.8) and reviewed before submission.

GetSCSITaskInterface() derives the BSD name from the drive argument but never
compares it against anything. The loop takes the first SCSITaskAuthoringDevice
IOKit hands out and breaks; the name is then used only for the
"diskutil unmountDisk" call. DiscImageCreator therefore unmounts the drive the
user asked for and dumps whichever drive happened to be enumerated first - it
runs to the end, reports no error, and writes files named after the requested
drive that hold the contents of a different disc.

Collect the authoring devices and pick the requested one out of them:

- By BSD name ("disk4") when a disc is loaded. The BSD name is not a property of
  the SCSI peripheral device; it belongs to the IOMedia object that the block
  storage stack publishes below it, so it has to be searched for recursively.
- By drive name ("PX-760A", "PLEXTOR", case insensitive) otherwise. macOS gives a
  drive no BSD name while it holds no disc, so eject/close/start/stop would have
  nothing left to address an empty drive by. The vendor and product from the
  device characteristics are there either way. A name that fits more than one
  drive is refused rather than guessed.

When nothing matches, report the drives that are there instead of falling back to
an arbitrary one, so the user can see what to ask for:

    Not found the drive: disk1
    Available drive:
            disk4     PLEXTOR DVDR   PX-760A

Unmount the drive that is about to be opened rather than the string that was
passed in. Unmounting the wrong drive left the opened one mounted, and
ObtainExclusiveAccess then failed with kIOReturnBusy.

Only the __APPLE__ branch is touched; Linux and Windows are unaffected.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
@saramibreak
saramibreak merged commit df85096 into saramibreak:master Jul 20, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants