/* * $Id$ * * sys_darwin.cpp - Extra Darwin system dependant routines. Called by: * * sys_unix.cpp - System dependent routines, Unix implementation * * Based on Apple's CDROMSample.c and Evan Jones' cd-discid.c patches * * Basilisk II (C) 1997-2008 Christian Bauer * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #import #import #import #import #import #import #import #ifdef HAVE_IOKIT_STORAGE_IOBLOCKSTORAGEDEVICE_H #import #endif #import #import #import #include "sysdeps.h" #include "sys.h" #include "prefs.h" #define DEBUG 0 #import "debug.h" // Global variables static volatile CFRunLoopRef media_poll_loop = NULL; static bool media_thread_active = false; static pthread_t media_thread; // Prototypes static void *media_poll_func(void *); // From sys_unix.cpp extern void SysMediaArrived(const char *path, int type); extern void SysMediaRemoved(const char *path, int type); /* * Initialization */ void DarwinSysInit(void) { if (!PrefsFindBool("nocdrom")) { media_thread_active = (pthread_create(&media_thread, NULL, media_poll_func, NULL) == 0); D(bug("Media poll thread installed (%ld)\n", media_thread)); } } /* * Deinitialization */ void DarwinSysExit(void) { // Stop media poll thread if (media_thread_active) { while (media_poll_loop == NULL || !CFRunLoopIsWaiting(media_poll_loop)) usleep(0); CFRunLoopStop(media_poll_loop); pthread_join(media_thread, NULL); media_poll_loop = NULL; media_thread_active = false; } } /* * Get the BSD-style path of specified object */ static kern_return_t get_device_path(io_object_t obj, char *path, size_t maxPathLength) { kern_return_t kernResult = KERN_FAILURE; CFTypeRef pathAsCFString = IORegistryEntryCreateCFProperty(obj, CFSTR(kIOBSDNameKey), kCFAllocatorDefault, 0); if (pathAsCFString) { strcpy(path, "/dev/"); size_t pathLength = strlen(path); if (CFStringGetCString((const __CFString *)pathAsCFString, path + pathLength, maxPathLength - pathLength, kCFStringEncodingASCII)) kernResult = KERN_SUCCESS; CFRelease(pathAsCFString); } return kernResult; } /* * kIOMatchedNotification handler */ static void media_arrived(int type, io_iterator_t iterator) { io_object_t obj; while ((obj = IOIteratorNext(iterator))) { char path[MAXPATHLEN]; kern_return_t kernResult = get_device_path(obj, path, sizeof(path)); if (kernResult == KERN_SUCCESS) { D(bug("Media Arrived: %s\n", path)); SysMediaArrived(path, type); } kernResult = IOObjectRelease(obj); if (kernResult != KERN_SUCCESS) { fprintf(stderr, "IOObjectRelease() returned %d\n", kernResult); } } } /* * kIOTerminatedNotification handler */ static void media_removed(int type, io_iterator_t iterator) { io_object_t obj; while ((obj = IOIteratorNext(iterator))) { char path[MAXPATHLEN]; kern_return_t kernResult = get_device_path(obj, path, sizeof(path)); if (kernResult == KERN_SUCCESS) { D(bug("Media Removed: %s\n", path)); SysMediaRemoved(path, type); } kernResult = IOObjectRelease(obj); if (kernResult != KERN_SUCCESS) { fprintf(stderr, "IOObjectRelease() returned %d\n", kernResult); } } } /* * Media poll function * * NOTE: to facilitate orderly thread termination, media_poll_func MUST end up waiting in CFRunLoopRun. * Early returns must be avoided, even if there is nothing useful to be done here. See DarwinSysExit. */ static void dummy(void *) { }; // stub for dummy runloop source static void *media_poll_func(void *) { media_poll_loop = CFRunLoopGetCurrent(); mach_port_t masterPort; kern_return_t kernResult; CFMutableDictionaryRef matchingDictionary; CFRunLoopSourceRef loopSource = NULL; CFRunLoopSourceRef dummySource = NULL; if ((kernResult = IOMasterPort(bootstrap_port, &masterPort)) != KERN_SUCCESS) fprintf(stderr, "IOMasterPort() returned %d\n", kernResult); else if ((matchingDictionary = IOServiceMatching(kIOCDMediaClass)) == NULL) fprintf(stderr, "IOServiceMatching() returned a NULL dictionary\n"); else { matchingDictionary = (CFMutableDictionaryRef)CFRetain(matchingDictionary); IONotificationPortRef notificationPort = IONotificationPortCreate(kIOMasterPortDefault); loopSource = IONotificationPortGetRunLoopSource(notificationPort); CFRunLoopAddSource(media_poll_loop, loopSource, kCFRunLoopDefaultMode); io_iterator_t mediaArrivedIterator; kernResult = IOServiceAddMatchingNotification(notificationPort, kIOMatchedNotification, matchingDictionary, (IOServiceMatchingCallback)media_arrived, (void *)MEDIA_CD, &mediaArrivedIterator); if (kernResult != KERN_SUCCESS) fprintf(stderr, "IOServiceAddMatchingNotification() returned %d\n", kernResult); media_arrived(MEDIA_CD, mediaArrivedIterator); io_iterator_t mediaRemovedIterator; kernResult = IOServiceAddMatchingNotification(notificationPort, kIOTerminatedNotification, matchingDictionary, (IOServiceMatchingCallback)media_removed, (void *)MEDIA_CD, &mediaRemovedIterator); if (kernResult != KERN_SUCCESS) fprintf(stderr, "IOServiceAddMatchingNotification() returned %d\n", kernResult); media_removed(MEDIA_CD, mediaRemovedIterator); } if (loopSource == NULL) { // add a dummy runloop source to prevent premature return from CFRunLoopRun CFRunLoopSourceContext context = { 0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, dummy }; dummySource = CFRunLoopSourceCreate(NULL, 0, &context); CFRunLoopAddSource(media_poll_loop, dummySource, kCFRunLoopDefaultMode); } CFRunLoopRun(); if (dummySource != NULL) CFRelease(dummySource); return NULL; } void DarwinAddFloppyPrefs(void) { mach_port_t masterPort; // The way to talk to the kernel io_iterator_t allFloppies; // List of possible floppys CFMutableDictionaryRef classesToMatch; io_object_t nextFloppy; if ( IOMasterPort(MACH_PORT_NULL, &masterPort) != KERN_SUCCESS ) bug("IOMasterPort failed. Won't be able to do anything with floppy drives\n"); // This selects all partitions of all disks classesToMatch = IOServiceMatching(kIOMediaClass); if ( classesToMatch ) { // Skip drivers and partitions CFDictionarySetValue(classesToMatch, CFSTR(kIOMediaWholeKey), kCFBooleanTrue); // Skip fixed drives (hard disks?) CFDictionarySetValue(classesToMatch, CFSTR(kIOMediaEjectableKey), kCFBooleanTrue); } if ( IOServiceGetMatchingServices(masterPort, classesToMatch, &allFloppies) != KERN_SUCCESS ) { D(bug("IOServiceGetMatchingServices failed. No removable drives found?\n")); return; } // Iterate through each floppy while ((nextFloppy = IOIteratorNext(allFloppies))) { char bsdPath[MAXPATHLEN]; long size = 0; Boolean gotSize = FALSE; CFTypeRef sizeAsCFNumber = IORegistryEntryCreateCFProperty(nextFloppy, CFSTR(kIOMediaSizeKey), kCFAllocatorDefault, 0); if (sizeAsCFNumber) { gotSize = CFNumberGetValue((CFNumberRef)sizeAsCFNumber, kCFNumberSInt32Type, &size); CFRelease(sizeAsCFNumber); } if (gotSize) { D(bug("Got size of %ld\n", size)); if ( size < 800 * 1024 || size > 1440 * 1024 ) { D(puts("Device does not appear to be 800k or 1440k")); continue; } } else { D(puts("Couldn't get kIOMediaSizeKey of device")); continue; // if kIOMediaSizeKey is unavailable, we shouldn't use it anyway } if (get_device_path(nextFloppy, bsdPath, sizeof(bsdPath)) == KERN_SUCCESS) { PrefsAddString("floppy", bsdPath); } else { D(bug("Could not get BSD device path for floppy\n")); } } IOObjectRelease(nextFloppy); IOObjectRelease(allFloppies); } void DarwinAddSerialPrefs(void) { mach_port_t masterPort; // The way to talk to the kernel io_iterator_t allModems; // List of modems on the system CFMutableDictionaryRef classesToMatch; io_object_t nextModem; if ( IOMasterPort(MACH_PORT_NULL, &masterPort) != KERN_SUCCESS ) bug("IOMasterPort failed. Won't be able to do anything with modems\n"); // Serial devices are instances of class IOSerialBSDClient classesToMatch = IOServiceMatching(kIOSerialBSDServiceValue); if ( classesToMatch ) { // Narrow the search a little further. Each serial device object has // a property with key kIOSerialBSDTypeKey and a value that is one of // kIOSerialBSDAllTypes, kIOSerialBSDModemType, or kIOSerialBSDRS232Type. CFDictionarySetValue(classesToMatch, CFSTR(kIOSerialBSDTypeKey), CFSTR(kIOSerialBSDModemType)); // This will find built-in and USB modems, but not serial modems. } if ( IOServiceGetMatchingServices(masterPort, classesToMatch, &allModems) != KERN_SUCCESS ) { D(bug("IOServiceGetMatchingServices failed. No modems found?\n")); return; } // Iterate through each modem while ((nextModem = IOIteratorNext(allModems))) { char bsdPath[MAXPATHLEN]; CFTypeRef bsdPathAsCFString = IORegistryEntryCreateCFProperty(nextModem, CFSTR(kIOCalloutDeviceKey), // kIODialinDeviceKey? kCFAllocatorDefault, 0); *bsdPath = '\0'; if ( bsdPathAsCFString ) { if ( CFStringGetCString((const __CFString *)bsdPathAsCFString, bsdPath, MAXPATHLEN, kCFStringEncodingASCII) ) { D(bug("Modem BSD path: %s\n", bsdPath)); // Note that if there are multiple modems, we only get the first PrefsAddString("seriala", bsdPath); break; } else D(bug("Could not get BSD device path for modem\n")); CFRelease(bsdPathAsCFString); } else D(puts("Cannot determine bsdPath for modem\n")); } IOObjectRelease(nextModem); IOObjectRelease(allModems); // Getting a printer device is a bit harder. Creating a fake device // that emulates a simple printer (e.g. a HP DeskJet) is one possibility, // but for now I will just create a fake, safe, device entry: PrefsAddString("serialb", "/dev/null"); } #ifdef MAC_OS_X_VERSION_10_2 /* * Read CD-ROM TOC (binary MSF format, 804 bytes max.) */ bool DarwinCDReadTOC(char *name, uint8 *toc) { char *c, *devname; int fd; // The open filehandle is something like /dev/disk5s1 // The DKIOCCDREADTOC ioctl needs the original cd file, // so we strip the s1 suffix off it, and open the file just for this ioctl devname = strdup(name); if ( ! devname ) return false; for ( c = devname; *c; ++c ) ; // Go to the end of the name, --c, --c; // point to the 's1' on the end, *c = '\0'; // and truncate the string fd = open(devname, O_RDONLY); if ( ! fd ) { printf("Failed to open CD device %s for ioctl\n", devname); free(devname); return false; } D(bug("Opened %s for ioctl()\n", devname)); dk_cd_read_toc_t TOCrequest; // Setup the ioctl request structure: memset(&TOCrequest, 0, sizeof(TOCrequest)); TOCrequest.buffer = toc; TOCrequest.bufferLength = 804; TOCrequest.formatAsTime = kCDTrackInfoAddressTypeTrackNumber; if ( ioctl(fd, DKIOCCDREADTOC, &TOCrequest) < 0 ) { printf("ioctl(DKIOCCDREADTOC) failed: %s\n", strerror(errno)); close(fd); free(devname); return false; } if ( TOCrequest.bufferLength < sizeof(CDTOC) ) { printf("ioctl(DKIOCCDREADTOC): only read %d bytes (a CDTOC is at least %d)\n", TOCrequest.bufferLength, (int)sizeof(CDTOC)); close(fd); free(devname); return false; } D(bug("ioctl(DKIOCCDREADTOC) read %d bytes\n", TOCrequest.bufferLength)); close(fd); free(devname); return true; } #endif