Create a gist now

Instantly share code, notes, and snippets.

Workaround for NTFS-3G and Lion's CoreFoundation bug
#!/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 $?
/*-
* 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;
}
}
@bfleischer
Owner

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.

sudo mv /usr/local/bin/fuse_wait /usr/local/bin/fuse_wait.orig
chmod +x build.sh
./build.sh . /tmp /

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:

sudo mv -f /usr/local/bin/fuse_wait.orig /usr/local/bin/fuse_wait

I hope this helps.

@bfleischer
Owner

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.

@harrypeh

It definitely solved the issue ! where the first mount of the NTFS device always failed ! Thanks for the installer package !

@songsta
songsta commented Apr 15, 2012

Yes, thanks!

@JanimeRed

Thank you a lot!! now I don't have problems with my windows partition!!

@johnniewalkerde

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