Created
July 22, 2011 20:14
-
-
Save bfleischer/1100318 to your computer and use it in GitHub Desktop.
Workaround for NTFS-3G and Lion's CoreFoundation bug
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/sh -x | |
# These variables are always passed to build.sh | |
DIST_DIR="$1" | |
TMP_DIR="$2" | |
ROOT_DIR="$3" | |
PROJNAME="fuse_wait" | |
DESTDIR="usr/local/bin" | |
CC=llvm-gcc | |
OSX_TARGET=10.7 | |
SDK=/Developer/SDKs/MacOSX10.7.sdk | |
TARGET_FLAGS="-arch i386 ${OSX_TARGET:+-mmacosx-version-min=${OSX_TARGET}}" | |
COMPILE_FLAGS="${TARGET_FLAGS} ${SDK:+-isysroot ${SDK}} -Wl,-syslibroot,${SDK}} -g -Wall -fconstant-cfstrings -framework CoreFoundation" | |
${CC} ${COMPILE_FLAGS} -o "${TMP_DIR}/${PROJNAME}" "${DIST_DIR}/${PROJNAME}.c" && \ | |
sudo cp "${TMP_DIR}/${PROJNAME}" "${ROOT_DIR}/${DESTDIR}" && \ | |
sudo chmod 755 "${ROOT_DIR}/${DESTDIR}/${PROJNAME}" && \ | |
sudo chown root:wheel "${ROOT_DIR}/${DESTDIR}/${PROJNAME}" | |
exit $? |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/*- | |
* fuse_wait - Light wrapper around a FUSE mount program that waits | |
* for the "mounted" notification before exiting. | |
* | |
* Copyright (C) 2007-2009 Erik Larsson | |
* | |
* 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., 51 Franklin Street, Fifth Floor, Boston, MA | |
* 02110-1301, USA | |
*/ | |
/* | |
* fuse_wait | |
* Written as a replacement to shadowofged's little utility. | |
* This tool executes a specified mount program with all of its | |
* arguments. The path to the mount program and all its arguments | |
* start at the third argument. The first argument is the mount | |
* point to check for mount notification, and the second argument | |
* is the amount of seconds to wait until timeout. | |
* Primarily used for mounting ntfs-3g filesystems on Mac OS X. | |
* | |
* Updated 2007-10-07: | |
* I didn't read the man page for waitpid correctly. fuse_wait | |
* thus returned incorrect exit values, but it's fixed now. | |
* | |
* Updated | |
*/ | |
#include <stdio.h> | |
#include <unistd.h> | |
#include <stdlib.h> // for realpath | |
#include <CoreFoundation/CoreFoundation.h> | |
#include <AvailabilityMacros.h> | |
#ifndef MAC_OS_X_VERSION_10_7 | |
#define MAC_OS_X_VERSION_10_7 1070 | |
#endif | |
#define VERSION "20090819" | |
#define FUSE_LISTEN_OBJECT "com.google.filesystems.fusefs.unotifications" | |
#define FUSE_MOUNT_NOTIFICATION_NAME "com.google.filesystems.fusefs.unotifications.mounted" | |
#define FUSE_MOUNT_PATH_KEY "kFUSEMountPath" | |
#ifdef DEBUG | |
#define DEBUGMODE TRUE | |
#else | |
#define DEBUGMODE FALSE | |
#endif | |
#if DEBUGMODE | |
#define LOG_DEBUG(...) do { FILE *debugFile = stderr; fprintf(debugFile, __VA_ARGS__); fflush(debugFile); } while(0) | |
#else | |
#define LOG_DEBUG(...) | |
#endif | |
#define MIN(X, Y) X < Y ? X : Y | |
static CFStringRef mountPath = NULL; | |
/* There seems to be a bug with MacFUSE such that the CFString that | |
* it returns for kFUSEMountPath in the dictionary accompanying the | |
* com.google.filesystems.fusefs.unotifications.mounted notification | |
* is represented as UTF-8 within the internal representation. | |
* Probably UTF-8 encoded text represented as UTF-16 values. This | |
* function transforms it to a true null terminated UTF-8 string. | |
* This function assumes that the target buffer out is at least | |
* CFStringGetLength(in) bytes long. */ | |
static void GetCorruptedMacFUSEStringAsUTF8(CFStringRef in, char* out, int outsize) { | |
int inLength = CFStringGetLength(in); | |
int bytesToWrite = MIN(inLength, outsize-1); | |
int i; | |
for(i = 0; i < bytesToWrite; ++i) | |
out[i] = (char)CFStringGetCharacterAtIndex(in, i); | |
out[i] = '\0'; | |
} | |
/* Debug code for printing entries in the CFDictionary 'userInfo'. */ | |
static void PrintDictEntry(const void *key, const void *value, void *context) { | |
char *tmp = NULL; | |
char *buffer = calloc(1, 512); | |
CFStringGetCString(key, buffer, 512, kCFStringEncodingUTF8); | |
LOG_DEBUG(" Key:\n"); | |
LOG_DEBUG(" \"%s\"", buffer); | |
CFStringGetCString(value, buffer, 512, kCFStringEncodingUTF8); | |
LOG_DEBUG(" Value:\n"); | |
LOG_DEBUG(" \"%s\"\n", buffer); | |
LOG_DEBUG(" Hex:\n"); | |
LOG_DEBUG(" "); | |
for(tmp = buffer; *tmp != '\0'; ++tmp) | |
LOG_DEBUG(" %s%hhX", ((*tmp & 0xFF) < 0x10 ? "0" : ""), *tmp); | |
LOG_DEBUG("\n"); | |
GetCorruptedMacFUSEStringAsUTF8(value, buffer, 512); | |
LOG_DEBUG(" Value (de-corrupted):\n"); | |
LOG_DEBUG(" \"%s\"\n", buffer); | |
LOG_DEBUG(" Hex (de-corrupted):\n"); | |
LOG_DEBUG(" "); | |
for(tmp = buffer; *tmp != '\0'; ++tmp) | |
LOG_DEBUG(" %s%hhX", ((*tmp & 0xFF) < 0x10 ? "0" : ""), *tmp); | |
LOG_DEBUG("\n"); | |
free(buffer); | |
/* FILE *dbgoutput = fopen("valuedump-utf32be.txt", "w"); */ | |
/* memset(buffer, 0, 512); */ | |
/* CFStringGetCString(value, buffer, 512, kCFStringEncodingUTF32BE); */ | |
/* fwrite(buffer, 512, 1, dbgoutput); */ | |
/* fclose(dbgoutput); */ | |
} | |
/* Callback function which will recieve the 'mounted' notification | |
* from FUSE. It is full with debug code... but I'll let it stay | |
* that way. */ | |
static void NotificationCallback(CFNotificationCenterRef center, | |
void *observer, | |
CFStringRef name, | |
const void *object, | |
CFDictionaryRef userInfo) { | |
if(DEBUGMODE) { | |
char buffer[512]; | |
LOG_DEBUG("Received notification:\n"); | |
if(CFStringGetCString(name, buffer, 512, kCFStringEncodingUTF8) == true) | |
LOG_DEBUG(" Name: %s\n", buffer); | |
else | |
LOG_DEBUG(" <Cound not get name>\n"); | |
LOG_DEBUG(" userInfo:\n"); | |
if(userInfo != NULL) | |
CFDictionaryApplyFunction(userInfo, PrintDictEntry, NULL); | |
else | |
LOG_DEBUG(" <null>\n"); | |
} | |
if(userInfo != NULL) { // It's only null when testing | |
const void *value = NULL; | |
LOG_DEBUG("CFDictionaryGetValueIfPresent(%p, \"%s\", %p)\n", | |
userInfo, FUSE_MOUNT_PATH_KEY, &value); | |
if(CFDictionaryGetValueIfPresent(userInfo, CFSTR(FUSE_MOUNT_PATH_KEY), | |
&value) == true) { | |
LOG_DEBUG("CFStringGetTypeID(%p) == %lu ?\n", value, | |
CFStringGetTypeID()); | |
if(CFGetTypeID((CFStringRef)value) == CFStringGetTypeID()) { | |
LOG_DEBUG(" yes.\n"); | |
LOG_DEBUG("mountPath=%p\n", mountPath); | |
if(mountPath != NULL) | |
CFRelease(mountPath); // No memory leaks please. | |
LOG_DEBUG("assigning mountpath the value %p\n", value); | |
mountPath = (CFStringRef)value; | |
CFRetain(mountPath); | |
LOG_DEBUG("done with assigning.\n"); | |
#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_7 | |
CFRunLoopStop(CFRunLoopGetCurrent()); | |
#endif | |
} | |
else | |
LOG_DEBUG(" no."); | |
} | |
} | |
} | |
/* Prints a help text to a stream. */ | |
static void PrintUsage(FILE *stream) { | |
/* 80 chars <-------------------------------------------------------------------------------->*/ | |
fprintf(stream, "fuse_wait (version " VERSION ") - Wait for MacFUSE mount completion.\n"); | |
fprintf(stream, "Copyright (C) 2007-2009 Erik Larsson / Tuxera Ltd.\n"); | |
fprintf(stream, "\n"); | |
fprintf(stream, "usage: fuse_wait <mountpoint> <timeout> <mount_command> [<args...>]\n"); | |
fprintf(stream, " mountpoint - where you wish to mount the MacFUSE file system\n"); | |
fprintf(stream, " timeout - time (in seconds) to wait for the mount operation to\n"); | |
fprintf(stream, " complete (may be a floating point value)\n"); | |
fprintf(stream, " mount_command - a path to the executable file containing the fuse program\n"); | |
fprintf(stream, " used to mount the file system\n"); | |
fprintf(stream, " args - the arguments that you would normally pass to <mount_command>\n"); | |
fprintf(stream, " (note that this includes the mount point)\n"); | |
} | |
int main(int argc, char** argv) { | |
LOG_DEBUG("[Debug messages enabled]\n"); | |
if(argc < 4) { | |
PrintUsage(stdout); | |
return 0; | |
} | |
/* <argv parsing> */ | |
/* Parse argument: mountpoint (CFString) */ | |
/*CFStringRef mountpoint = CFStringCreateWithCString(kCFAllocatorDefault, | |
argv[1], kCFStringEncodingUTF8); */ | |
/* Parse argument: mountpointRaw (char*) */ | |
char *mountpointRaw = argv[1]; | |
/* Parse argument: timeout (double) */ | |
double timeout; | |
{ | |
CFStringRef timeoutString = CFStringCreateWithCString(kCFAllocatorDefault, | |
argv[2], | |
kCFStringEncodingUTF8); | |
timeout = CFStringGetDoubleValue(timeoutString); | |
CFRelease(timeoutString); | |
if(timeout == 0.0) { | |
fprintf(stdout, "Invalid argument: timeout (\"%s\")\n", argv[2]); | |
return -1; | |
} | |
} | |
/* Parse argument: mount_command */ | |
const char *mount_command = argv[3]; | |
/* </argv parsing> */ | |
CFNotificationCenterRef centerRef; | |
CFStringRef notificationObjectName = CFSTR(FUSE_LISTEN_OBJECT); | |
CFStringRef notificationName = CFSTR(FUSE_MOUNT_NOTIFICATION_NAME); | |
//CFRunLoopRef runLoop; | |
if(DEBUGMODE) { | |
LOG_DEBUG("Testing NotificationCallback...\n"); | |
NotificationCallback(NULL, NULL, notificationObjectName, NULL, NULL); | |
LOG_DEBUG("Test completed. Adding observer...\n"); | |
} | |
centerRef = CFNotificationCenterGetDistributedCenter(); | |
//runLoop = CFRunLoopGetCurrent(); | |
/* | |
* We need to add ourselves as an observer before the fork, in case the forked | |
* process outruns us. | |
* | |
* Will the child process also be an observer? Possibly, but even if that is | |
* the case, it shouldn't seem to make any practical difference (we never | |
* enter a CFRunLoop in the child process, at least not before execvp). | |
*/ | |
CFNotificationCenterAddObserver(centerRef, NULL, NotificationCallback, notificationName, | |
notificationObjectName, CFNotificationSuspensionBehaviorDrop); | |
int forkRetval = fork(); | |
if(forkRetval == -1) { | |
fprintf(stderr, "Could not fork!\n"); | |
return -1; | |
} | |
else if(forkRetval != 0) { | |
// Parent process | |
int childProcessPID = forkRetval; | |
int waitpid_status = 0; | |
LOG_DEBUG("Waiting for PID %d...\n", childProcessPID); | |
int waitpidres = waitpid(childProcessPID, &waitpid_status, 0); | |
if(waitpidres == childProcessPID) { | |
if(!WIFEXITED(waitpid_status)) { | |
LOG_DEBUG("Child process did not exit cleanly! Returning -1."); | |
return -1; | |
} | |
int retval = WEXITSTATUS(waitpid_status); | |
LOG_DEBUG("PID %d returned with exit code: %d Exiting fuse_wait with this exit code...\n", childProcessPID, retval); | |
if(retval != 0) { | |
LOG_DEBUG("Exit value indicates an error while executing mount command. Returning without\n"); | |
LOG_DEBUG("waiting for notification.\n"); | |
LOG_DEBUG("Returning retval: %d\n", retval); | |
return retval; | |
} | |
} | |
else { | |
LOG_DEBUG("Abnormal termination of process %d. waitpid returned: %d\n", childProcessPID, waitpidres); | |
return -1; | |
} | |
LOG_DEBUG("Running run loop a long time...\n"); | |
CFStringRef mountPathSnapshot = NULL; | |
while(mountPathSnapshot == NULL) { | |
#if MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_7 | |
int crlrimRetval = CFRunLoopRunInMode(kCFRunLoopDefaultMode, timeout, true); | |
LOG_DEBUG("Exited from run loop. Let's find out why... crlrimRetval: %d " | |
"(handled: %d)\n", crlrimRetval, kCFRunLoopRunHandledSource); | |
#else | |
CFRunLoopRunInMode(kCFRunLoopDefaultMode, timeout, true); | |
LOG_DEBUG("Exited from run loop. Let's find out why.\n"); | |
#endif | |
mountPathSnapshot = mountPath; // Might have been modified during RunLoop. | |
#if MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_7 | |
if(crlrimRetval != kCFRunLoopRunHandledSource) { | |
fprintf(stderr, "Did not receive a signal within %f seconds. " | |
"Exiting...\n", timeout); | |
return -2; | |
} | |
#endif | |
if(mountPathSnapshot != NULL) { | |
LOG_DEBUG("mountPathSnapshot: %p\n", mountPathSnapshot); | |
/* | |
* Convert the CFString that we got back from the CFNotificationCenter | |
* into UTF-8 data. (We assume that UTF-8 is the encoding used by the | |
* system, which is true in Mac OS X.) | |
*/ | |
CFDataRef utf8Data = | |
CFStringCreateExternalRepresentation(NULL, mountPath, | |
kCFStringEncodingUTF8, 0); | |
if(utf8Data != NULL) { | |
/* | |
* Put the UTF-8 data in a NULL-terminated C-string. | |
*/ | |
size_t mountPathUTF8Length = CFDataGetLength(utf8Data) + 1; | |
char *mountPathUTF8 = calloc(1, mountPathUTF8Length); | |
CFDataGetBytes(utf8Data, CFRangeMake(0, mountPathUTF8Length - 1), | |
(unsigned char*) mountPathUTF8); | |
//int mountPathUTF8Length = CFStringGetLength(mountPath) + 1; // null terminator | |
//char *mountPathUTF8 = malloc(mountPathUTF8Length); | |
//memset(mountPathUTF8, 0, mountPathUTF8Length); | |
//GetCorruptedMacFUSEStringAsUTF8(mountPath, mountPathUTF8, mountPathUTF8Length); | |
/* | |
* Canonicalize the two paths, in case one of the pathnames contain | |
* symbolic links, but not the other. | |
*/ | |
char *canonicalMountPath = malloc(PATH_MAX); | |
char *canonicalMountpoint = malloc(PATH_MAX); | |
memset(canonicalMountPath, 0, PATH_MAX); | |
memset(canonicalMountpoint, 0, PATH_MAX); | |
realpath(mountPathUTF8, canonicalMountPath); | |
realpath(mountpointRaw, canonicalMountpoint); | |
/* | |
* Finally, compare the two paths for equality. | |
*/ | |
int cmpres = strncmp(canonicalMountPath, canonicalMountpoint, PATH_MAX); | |
if(cmpres != 0) { | |
LOG_DEBUG("Strings NOT equal. cmpres=%d\n", cmpres); | |
LOG_DEBUG("mountPath (UTF-8): \"%s\"\n", canonicalMountPath); | |
LOG_DEBUG("mountpoint (UTF-8): \"%s\"\n", canonicalMountpoint); | |
mountPathSnapshot = NULL; | |
} | |
else | |
LOG_DEBUG("Recieved a signal for the mount point.\n"); | |
free(mountPathUTF8); | |
free(canonicalMountPath); | |
free(canonicalMountpoint); | |
} | |
CFRelease(utf8Data); | |
} | |
} | |
LOG_DEBUG("Run loop done.\n"); | |
if(mountPath != NULL) | |
CFRelease(mountPath); | |
return 0; // We have previously checked that the return value from the child process is 0. We can't get here if it isn't. | |
} | |
else { // forkRetval == 0 | |
// Child process | |
const int childargc = argc-3; | |
char *childargv[childargc+1]; | |
childargv[childargc] = NULL; // Null terminated | |
int i; | |
for(i = 0; i < childargc; ++i) | |
childargv[i] = argv[i+(argc-childargc)]; | |
LOG_DEBUG("Contents of argv:\n"); | |
for(i = 0; i < argc; ++i) | |
LOG_DEBUG(" argv[%i]: \"%s\"\n", i, argv[i]); | |
LOG_DEBUG("Contents of childargv:\n"); | |
for(i = 0; i < childargc; ++i) | |
LOG_DEBUG(" childargv[%i]: \"%s\"\n", i, childargv[i]); | |
execvp(mount_command, childargv); | |
fprintf(stderr, "Could not execute %s!\n", argv[3]); | |
return -1; | |
} | |
} |
To make the process of fixing this issue easier, I created an installer package containing the patched version of fuse_wait
for Mac OS X 10.7. No need to compile anything, just install the package and you are good to go. The package can be downloaded at https://github.com/bfleischer/fuse_wait/downloads.
See https://github.com/bfleischer/fuse_wait for more details.
It definitely solved the issue ! where the first mount of the NTFS device always failed ! Thanks for the installer package !
Yes, thanks!
Thank you a lot!! now I don't have problems with my windows partition!!
Thank you for the fix! That timeout was very annoying.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Because of a change in Mac OS X 10.7,
fuse_wait
(a tool distributed with NTFS-3G for Mac OS X) displays a "15 seconds" timeout error every time a NTFS volume is mounted.Download this gist and run the following commands to build and install this unofficial version of
fuse_wait
, that fixes the issue. Please note that Xcode has to be installed for this to work.NTFS-3G and other filesystems using
fuse_wait
should now mount fine (without the timeout error) and stay mounted. Don't forget that this is just a workaround. This issue has to be fixed in an updated version of NTFS-3G.To revert to the original
fuse_wait
, run the following command in Terminal:I hope this helps.