Skip to content

Instantly share code, notes, and snippets.

@bfleischer
Created July 22, 2011 20:14
Show Gist options
  • Save bfleischer/1100318 to your computer and use it in GitHub Desktop.
Save bfleischer/1100318 to your computer and use it in GitHub Desktop.
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
Copy link
Author

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
Copy link
Author

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
Copy link

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

@songsta
Copy link

songsta commented Apr 15, 2012

Yes, thanks!

@JanimeRed
Copy link

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

@johnniewalkerde
Copy link

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