diff --git a/BasiliskII/src/MacOSX/ToDo.html b/BasiliskII/src/MacOSX/ToDo.html index 78afaefc..216d1536 100644 --- a/BasiliskII/src/MacOSX/ToDo.html +++ b/BasiliskII/src/MacOSX/ToDo.html @@ -10,8 +10,6 @@ Bugs: Disturbing, but not damaging.
  • Ejecting a CD only works in 10.2 or higher, and it freezes the emulation for about 5 seconds.
  • -
  • Inserting a second CD after ejecting the first one - doesn't currently remount the disk.
  • Status of 'dd' command is not always correct. (If it runs out of space, an error about file not found is printed?)
  • The Snapshot function is currently broken in some situations diff --git a/BasiliskII/src/MacOSX/sys_darwin.cpp b/BasiliskII/src/MacOSX/sys_darwin.cpp index 47f6dd4a..9778fceb 100644 --- a/BasiliskII/src/MacOSX/sys_darwin.cpp +++ b/BasiliskII/src/MacOSX/sys_darwin.cpp @@ -38,92 +38,169 @@ #import #import -#import "sysdeps.h" +#include "sysdeps.h" -#import "prefs.h" +#include "sys.h" +#include "prefs.h" #define DEBUG 0 #import "debug.h" +// Global variables +static 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); + /* - * This gets called when no "cdrom" prefs items are found - * It scans for available CD-ROM drives and adds appropriate prefs items + * Initialization */ -void DarwinAddCDROMPrefs(void) +void DarwinSysInit(void) { - mach_port_t masterPort; // The way to talk to the kernel - io_iterator_t allCDs; // List of CD drives on the system - CFMutableDictionaryRef classesToMatch; - io_object_t nextCD; + media_thread_active = (pthread_create(&media_thread, NULL, media_poll_func, NULL) == 0); + D(bug("Media poll thread installed (%ld)\n", media_thread)); +} - // Don't scan for drives if nocdrom option given - if ( PrefsFindBool("nocdrom") ) - return; +/* + * Deinitialization + */ + +void DarwinSysExit(void) +{ + // Stop media poll thread + if (media_poll_loop) + CFRunLoopStop(media_poll_loop); + if (media_thread_active) + pthread_join(media_thread, NULL); +} - // Let this task talk to the guts of the kernel: - if ( IOMasterPort(MACH_PORT_NULL, &masterPort) != KERN_SUCCESS ) - bug("IOMasterPort failed. Won't be able to do anything with CD drives\n"); +/* + * Get the BSD-style path of specified object + */ - - // CD media are instances of class kIOCDMediaClass - classesToMatch = IOServiceMatching(kIOCDMediaClass); - if ( classesToMatch ) - { - // Narrow the search a little further. Each IOMedia object - // has a property with key kIOMediaEjectable. We limit - // the match only to those CDs that are actually ejectable - CFDictionarySetValue(classesToMatch, - CFSTR(kIOMediaEjectableKey), kCFBooleanTrue); - } - - if ( IOServiceGetMatchingServices(masterPort, - classesToMatch, &allCDs) != KERN_SUCCESS ) - { - D(bug("IOServiceGetMatchingServices failed. No CD media drives found?\n")); - return; +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; +} - // Iterate through each CD drive - while ( nextCD = IOIteratorNext(allCDs)) - { - char bsdPath[MAXPATHLEN]; - CFTypeRef bsdPathAsCFString = - IORegistryEntryCreateCFProperty(nextCD, - CFSTR(kIOBSDNameKey), - kCFAllocatorDefault, 0); - *bsdPath = '\0'; - if ( bsdPathAsCFString ) - { - size_t devPathLength; +/* + * kIOMatchedNotification handler + */ - strcpy(bsdPath, "/dev/"); - devPathLength = strlen(bsdPath); - - if ( CFStringGetCString((const __CFString *)bsdPathAsCFString, - bsdPath + devPathLength, - MAXPATHLEN - devPathLength, - kCFStringEncodingASCII) ) - { - D(bug("CDROM BSD path: %s\n", bsdPath)); - PrefsAddString("cdrom", bsdPath); - } - else - D(bug("Could not get BSD device path for CD\n")); - - CFRelease(bsdPathAsCFString); +static void media_arrived(int type, io_iterator_t iterator) +{ + io_object_t obj; + while ((obj = IOIteratorNext(iterator)) != NULL) { + 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); } - else - D(bug("Cannot determine bsdPath for CD\n")); + 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)) != NULL) { + 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 + */ + +static void *media_poll_func(void *) +{ + media_poll_loop = CFRunLoopGetCurrent(); + + mach_port_t masterPort; + kern_return_t kernResult = IOMasterPort(bootstrap_port, &masterPort); + if (kernResult != KERN_SUCCESS) { + fprintf(stderr, "IOMasterPort() returned %d\n", kernResult); + return NULL; } - IOObjectRelease(nextCD); - IOObjectRelease(allCDs); + CFMutableDictionaryRef matchingDictionary = IOServiceMatching(kIOCDMediaClass); + if (matchingDictionary == NULL) { + fprintf(stderr, "IOServiceMatching() returned a NULL dictionary\n"); + return NULL; + } + matchingDictionary = (CFMutableDictionaryRef)CFRetain(matchingDictionary); + + IONotificationPortRef notificationPort = IONotificationPortCreate(kIOMasterPortDefault); + CFRunLoopAddSource(media_poll_loop, + IONotificationPortGetRunLoopSource(notificationPort), + 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); + + CFRunLoopRun(); + return NULL; } diff --git a/BasiliskII/src/Unix/sys_unix.cpp b/BasiliskII/src/Unix/sys_unix.cpp index de378b3b..5f4a02aa 100644 --- a/BasiliskII/src/Unix/sys_unix.cpp +++ b/BasiliskII/src/Unix/sys_unix.cpp @@ -41,6 +41,15 @@ #include #endif +#if defined __APPLE__ && defined __MACH__ +#include +#if (defined AQUA || defined HAVE_FRAMEWORK_COREFOUNDATION) +#ifndef __MACOSX__ +#define __MACOSX__ MAC_OS_X_VERSION_MIN_REQUIRED +#endif +#endif +#endif + #include "main.h" #include "macos_util.h" #include "prefs.h" @@ -61,6 +70,7 @@ struct file_handle { bool read_only; // Copy of Sys_open() flag loff_t start_byte; // Size of file header (if any) loff_t file_size; // Size of file data (only valid if is_file is true) + bool is_media_present; // Flag: media is inserted and available #if defined(__linux__) int cdrom_cap; // CD-ROM capability flags (only valid if is_cdrom is true) @@ -72,9 +82,20 @@ struct file_handle { #endif }; +// Open file handles +struct open_file_handle { + file_handle *fh; + open_file_handle *next; +}; +static open_file_handle *open_file_handles = NULL; + // File handle of first floppy drive (for SysMountFirstFloppy()) static file_handle *first_floppy = NULL; +// Prototypes +static void cdrom_close(file_handle *fh); +static bool cdrom_open(file_handle *fh, const char *path = NULL); + /* * Initialization @@ -82,6 +103,10 @@ static file_handle *first_floppy = NULL; void SysInit(void) { +#if defined __MACOSX__ + extern void DarwinSysInit(void); + DarwinSysInit(); +#endif } @@ -91,6 +116,113 @@ void SysInit(void) void SysExit(void) { +#if defined __MACOSX__ + extern void DarwinSysExit(void); + DarwinSysExit(); +#endif +} + + +/* + * Manage open file handles + */ + +static void sys_add_file_handle(file_handle *fh) +{ + open_file_handle *p = new open_file_handle; + p->fh = fh; + p->next = open_file_handles; + open_file_handles = p; +} + +static void sys_remove_file_handle(file_handle *fh) +{ + open_file_handle *p = open_file_handles; + open_file_handle *q = NULL; + + while (p) { + if (p->fh == fh) { + if (q) + q->next = p->next; + else + open_file_handles = p->next; + delete p; + break; + } + q = p; + p = p->next; + } +} + + +/* + * Account for media that has just arrived + */ + +void SysMediaArrived(const char *path, int type) +{ + // Replace the "cdrom" entry (we are polling, it's unique) + if (type == MEDIA_CD && !PrefsFindBool("nocdrom")) + PrefsReplaceString("cdrom", path); + + // Wait for media to be available for reading + if (open_file_handles) { + const int MAX_WAIT = 5; + for (int i = 0; i < MAX_WAIT; i++) { + if (access(path, R_OK) == 0) + break; + switch (errno) { + case ENOENT: // Unlikely + case EACCES: // MacOS X is mounting the media + sleep(1); + continue; + } + printf("WARNING: Cannot access %s (%s)\n", path, strerror(errno)); + return; + } + } + + for (open_file_handle *p = open_file_handles; p != NULL; p = p->next) { + file_handle * const fh = p->fh; + + // Re-open CD-ROM device + if (fh->is_cdrom && type == MEDIA_CD) { + cdrom_close(fh); + if (cdrom_open(fh, path)) { + fh->is_media_present = true; + MountVolume(fh); + } + } + } +} + + +/* + * Account for media that has just been removed + */ + +void SysMediaRemoved(const char *path, int type) +{ + if ((type & MEDIA_REMOVABLE) != MEDIA_CD) + return; + + for (open_file_handle *p = open_file_handles; p != NULL; p = p->next) { + file_handle * const fh = p->fh; + + // Mark media as not available + if (!fh->is_cdrom || !fh->is_media_present) + continue; + if (fh->name && strcmp(fh->name, path) == 0) { + fh->is_media_present = false; + break; + } +#if defined __MACOSX__ + if (fh->ioctl_name && strcmp(fh->ioctl_name, path) == 0) { + fh->is_media_present = false; + break; + } +#endif + } } @@ -211,15 +343,12 @@ void SysAddCDROMPrefs(void) closedir(cd_dir); } } -#elif defined(__APPLE__) && defined(__MACH__) - #if defined(AQUA) || defined(HAVE_FRAMEWORK_COREFOUNDATION) - extern void DarwinAddCDROMPrefs(void); - - DarwinAddCDROMPrefs(); - #else - // Until I can convince the other guys that my Darwin code is useful, - // we just do nothing (it is safe to have no cdrom device) - #endif +#elif defined __MACOSX__ + // There is no predefined path for CD-ROMs on MacOS X. Rather, we + // define a single fake CD-ROM entry for the emulated MacOS. + // XXX this means we handle only CD-ROM drive at a time, wherever + // the disk is, the latest one is used. + PrefsAddString("cdrom", "/dev/poll/cdrom"); #elif defined(__FreeBSD__) || defined(__NetBSD__) PrefsAddString("cdrom", "/dev/cd0c"); #endif @@ -261,6 +390,74 @@ void SysAddSerialPrefs(void) } +/* + * Open CD-ROM device and initialize internal data + */ + +static bool cdrom_open_1(file_handle *fh) +{ +#if defined __MACOSX__ + // In OS X, the device name is OK for sending ioctls to, + // but not for reading raw CDROM data from. + // (it seems to have extra data padded in) + // + // So, we keep the already opened file handle, + // and open a slightly different file for CDROM data + // + fh->ioctl_fd = fh->fd; + fh->ioctl_name = fh->name; + fh->fd = -1; + fh->name = (char *)malloc(strlen(fh->ioctl_name) + 3); + if (fh->name) { + strcpy(fh->name, fh->ioctl_name); + strcat(fh->name, "s1"); + fh->fd = open(fh->name, O_RDONLY, O_NONBLOCK); + } + if (fh->ioctl_fd < 0) + return false; +#endif + return true; +} + +bool cdrom_open(file_handle *fh, const char *path) +{ + if (path) + fh->name = strdup(path); + fh->fd = open(fh->name, O_RDONLY, O_NONBLOCK); + fh->start_byte = 0; + if (!cdrom_open_1(fh)) + return false; + return fh->fd >= 0; +} + + +/* + * Close a CD-ROM device + */ + +void cdrom_close(file_handle *fh) +{ + if (fh->fd >= 0) { + close(fh->fd); + fh->fd = -1; + } + if (fh->name) { + free(fh->name); + fh->name = NULL; + } +#if defined __MACOSX__ + if (fh->ioctl_fd >= 0) { + close(fh->ioctl_fd); + fh->ioctl_fd = -1; + } + if (fh->ioctl_name) { + free(fh->ioctl_name); + fh->ioctl_name = NULL; + } +#endif +} + + /* * Check if device is a mounted HFS volume, get mount name */ @@ -310,23 +507,23 @@ void *Sys_open(const char *name, bool read_only) #endif bool is_floppy = strncmp(name, "/dev/fd", 7) == 0; -#if defined(__APPLE__) && defined(__MACH__) - // + bool is_polled_media = strncmp(name, "/dev/poll/", 10) == 0; + if (is_floppy) // Floppy open fails if there's no disk inserted + is_polled_media = true; + +#if defined __MACOSX__ // There is no set filename in /dev which is the cdrom, // so we have to see if it is any of the devices that we found earlier - // - const char *cdrom; - int tmp = 0; - - while ( (cdrom = PrefsFindString("cdrom", tmp) ) != NULL ) { - if ( strcmp(name, cdrom) == 0 ) - { - is_cdrom = 1; - read_only = 1; - break; + int index = 0; + const char *str; + while ((str = PrefsFindString("cdrom", index++)) != NULL) { + if (is_polled_media || strcmp(str, name) == 0) { + is_cdrom = true; + read_only = true; + break; + } } - ++tmp; } #endif @@ -351,7 +548,7 @@ void *Sys_open(const char *name, bool read_only) } // Open file/device -#if defined(__linux__) || defined(__FreeBSD__) || defined(__NetBSD__) +#if defined(__linux__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__MACOSX__) int fd = open(name, (read_only ? O_RDONLY : O_RDWR) | (is_cdrom ? O_NONBLOCK : 0)); #else int fd = open(name, read_only ? O_RDONLY : O_RDWR); @@ -361,7 +558,7 @@ void *Sys_open(const char *name, bool read_only) read_only = true; fd = open(name, O_RDONLY); } - if (fd >= 0 || is_floppy) { // Floppy open fails if there's no disk inserted + if (fd >= 0 || is_polled_media) { file_handle *fh = new file_handle; fh->name = strdup(name); fh->fd = fd; @@ -370,7 +567,13 @@ void *Sys_open(const char *name, bool read_only) fh->start_byte = 0; fh->is_floppy = is_floppy; fh->is_cdrom = is_cdrom; + fh->is_media_present = false; +#if defined __MACOSX__ + fh->ioctl_fd = -1; + fh->ioctl_name = NULL; +#endif if (fh->is_file) { + fh->is_media_present = true; // Detect disk image file layout loff_t size = 0; size = lseek(fd, 0, SEEK_END); @@ -381,6 +584,7 @@ void *Sys_open(const char *name, bool read_only) } else { struct stat st; if (fstat(fd, &st) == 0) { + fh->is_media_present = true; if (S_ISBLK(st.st_mode)) { fh->is_cdrom = is_cdrom; #if defined(__linux__) @@ -408,39 +612,19 @@ void *Sys_open(const char *name, bool read_only) fh->is_floppy = ((st.st_rdev >> 16) == 2); #endif } -#if defined(__APPLE__) && defined(__MACH__) - - // In OS X, the device name is OK for sending ioctls to, - // but not for reading raw CDROM data from. - // (it seems to have extra data padded in) - // - // So, we keep the already opened fiole handle, - // and open a slightly different file for CDROM data - // - if ( is_cdrom ) - { - fh->ioctl_name = fh->name; - fh->ioctl_fd = fh->fd; - - fh->name = (char *) malloc(strlen(name) + 2); - if ( fh->name ) - { - *fh->name = '\0'; - strcat(fh->name, name); - strcat(fh->name, "s1"); - fh->fd = open(fh->name, (read_only ? O_RDONLY : O_RDWR)); - if ( fh->fd < 0 ) { - printf("WARNING: Cannot open %s (%s)\n", - fh->name, strerror(errno)); - return NULL; - } - } +#if defined __MACOSX__ + if (is_cdrom) { + fh->is_cdrom = true; + fh->is_floppy = false; + if (cdrom_open_1(fh)) + fh->is_media_present = true; } #endif } } if (fh->is_floppy && first_floppy == NULL) first_floppy = fh; + sys_add_file_handle(fh); return fh; } else { printf("WARNING: Cannot open %s (%s)\n", name, strerror(errno)); @@ -459,6 +643,10 @@ void Sys_close(void *arg) if (!fh) return; + sys_remove_file_handle(fh); + + if (fh->is_cdrom) + cdrom_close(fh); if (fh->fd >= 0) close(fh->fd); if (fh->name) @@ -526,6 +714,16 @@ loff_t SysGetFileSize(void *arg) return 0; D(bug(" BLKGETSIZE returns %d blocks\n", blocks)); return (loff_t)blocks * 512; +#elif defined __MACOSX__ + uint32 block_size; + if (ioctl(fh->ioctl_fd, DKIOCGETBLOCKSIZE, &block_size) < 0) + return 0; + D(bug(" DKIOCGETBLOCKSIZE returns %lu bytes\n", (unsigned long)block_size)); + uint64 block_count; + if (ioctl(fh->ioctl_fd, DKIOCGETBLOCKCOUNT, &block_count) < 0) + return 0; + D(bug(" DKIOCGETBLOCKCOUNT returns %llu blocks\n", (unsigned long long)block_count)); + return block_count * block_size; #else return lseek(fh->fd, 0, SEEK_END) - fh->start_byte; #endif @@ -566,31 +764,26 @@ void SysEject(void *arg) fh->fd = open(fh->name, O_RDONLY | O_NONBLOCK); } #elif defined(__APPLE__) && defined(__MACH__) - if ( fh->is_cdrom ) { - - // Stolen from IOKit/storage/IOMediaBSDClient.h - #define DKIOCEJECT _IO('d', 21) - + if (fh->is_cdrom && fh->is_media_present) { close(fh->fd); - if ( ioctl(fh->ioctl_fd, DKIOCEJECT) < 0 ) - { - printf("ioctl(DKIOCEJECT) failed on file %s: %s\n", - fh->ioctl_name, strerror(errno)); + fh->fd = -1; + if (ioctl(fh->ioctl_fd, DKIOCEJECT) < 0) { + D(bug(" DKIOCEJECT failed on file %s: %s\n", + fh->ioctl_name, strerror(errno))); - // If we are running OSX, the device may be is busy - // due to the Finder having the disk mounted and open, - // so we have to use another method. - - // The only problem is that this takes about 5 seconds: - - char *cmd = (char *) malloc(30+sizeof(fh->name)); - - if ( ! cmd ) - return; + // If we are running MacOS X, the device may be in busy + // state because the Finder has mounted the disk close(fh->ioctl_fd); - sprintf(cmd, "diskutil eject %s 2>&1 >/dev/null", fh->name); + fh->ioctl_fd = -1; + + // Try to use "diskutil eject" but it can take up to 5 + // seconds to complete + static const char eject_cmd[] = "/usr/sbin/diskutil eject %s 2>&1 >/dev/null"; + char *cmd = (char *)alloca(strlen(eject_cmd) + strlen(fh->ioctl_name) + 1); + sprintf(cmd, eject_cmd, fh->ioctl_name); system(cmd); } + fh->is_media_present = false; } #endif } @@ -703,6 +896,9 @@ bool SysIsDiskInserted(void *arg) } else if (fh->is_cdrom) { struct ioc_toc_header header; return ioctl(fh->fd, CDIOREADTOCHEADER, &header) == 0; +#elif defined __MACOSX__ + } else if (fh->is_cdrom || fh->is_floppy) { + return fh->is_media_present; #endif } else @@ -801,10 +997,12 @@ bool SysCDReadTOC(void *arg, uint8 *toc) *toc++ = toc_size >> 8; *toc++ = toc_size & 0xff; return true; -#elif defined(__APPLE__) && defined(__MACH__) && defined(MAC_OS_X_VERSION_10_2) && defined(HAVE_FRAMEWORK_COREFOUNDATION) - extern bool DarwinCDReadTOC(char *name, uint8 *toc); - - return DarwinCDReadTOC(fh->name, toc); +#elif defined __MACOSX__ && defined MAC_OS_X_VERSION_10_2 + if (fh->is_media_present) { + extern bool DarwinCDReadTOC(char *name, uint8 *toc); + return DarwinCDReadTOC(fh->name, toc); + } + return false; #elif defined(__FreeBSD__) uint8 *p = toc + 2; diff --git a/BasiliskII/src/Windows/sys_windows.cpp b/BasiliskII/src/Windows/sys_windows.cpp index 0c866e49..825926df 100755 --- a/BasiliskII/src/Windows/sys_windows.cpp +++ b/BasiliskII/src/Windows/sys_windows.cpp @@ -45,14 +45,6 @@ using std::min; #include "debug.h" -// Supported media types -enum { - MEDIA_FLOPPY = 1, - MEDIA_CD = 2, - MEDIA_HD = 4, - MEDIA_REMOVABLE = MEDIA_FLOPPY | MEDIA_CD -}; - // File handles are pointers to these structures struct file_handle { char *name; // Copy of device/file name diff --git a/BasiliskII/src/include/sys.h b/BasiliskII/src/include/sys.h index 1921bc91..7bb8096d 100644 --- a/BasiliskII/src/include/sys.h +++ b/BasiliskII/src/include/sys.h @@ -21,6 +21,14 @@ #ifndef SYS_H #define SYS_H +// Supported media types +enum { + MEDIA_FLOPPY = 1, + MEDIA_CD = 2, + MEDIA_HD = 4, + MEDIA_REMOVABLE = MEDIA_FLOPPY | MEDIA_CD +}; + extern void SysInit(void); extern void SysExit(void);