/*- * Copyright (C) 2007-2009 Erik Larsson / Tuxera Ltd. * * All rights reserved. * * 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 */ /* ntfs-3g_daemon * * This small program is designed to sit in the background and wait * until it gets the SIGTERM signal. At that point it activates, and * starts to iterate through all ntfs-3g file systems and unmounts * them, one by one. The reason for that this behaviour is needed is * the strange bug which makes ntfs-3g hang at shutdown. * It is assumed that noone will send the SIGTERM signal to this * program except for at shutdown time. (There's definitely room for * improvement here...) * * The ntfs-3g file systems are identified among the statfs structs * returned from getmntinfo by having the member f_fstypename set to * "fusefs" and the member f_reserved1 set to 8. f_reserved1 is * assumed to always hold the value of the MacFUSE subtype number, * where 8 corresponds to ntfs-3g. * This method has been determined experimentally and may not work * with future MacFUSE updates. * * 2009-08-24: Updated to compile on Snow Leopard. */ #include #include #include #include #include #include #include #include #define PROGRAM_NAME "ntfs-3g_daemon" #define FUSEFS_PREFIX "fusefs" #define FUSEFS_STRLEN 6 #define FUSEFS_NTFS_3G_SUBTYPE 8 #define PIDFILE_FILENAME "/var/run/" PROGRAM_NAME ".pid" #define TRUE 1 #define FALSE 0 /* Configure logging levels here. */ #define LOG_ENABLE_DEBUG FALSE #define LOG_ENABLE_ERROR TRUE #define LOG_ENABLE_FILE_LOGGING FALSE /* */ #if LOG_ENABLE_FILE_LOGGING #define LOG_TO_FILE(...) do { FILE *logFile = fopen("/var/log/" PROGRAM_NAME "_program.log", "a"); if(logFile != NULL) { fprintf(logFile, "%s ", gettimestring()); fprintf(logFile, __VA_ARGS__); fprintf(logFile, "\n"); fclose(logFile); } } while(0) /* Function that can be considered private to the LOG_TO_FILE macro. */ static inline char* gettimestring() { time_t logtime = time(NULL); char* timestring = ctime(&logtime); const int timestringlen = strlen(timestring); /* Cut the time string at first line break to make things look prettier. */ int i; for(i = 0; i < timestringlen; ++i) { if(timestring[i] == '\n' || timestring[i] == '\r') { timestring[i] = '\0'; break; } } return timestring; } #else #define LOG_TO_FILE(...) #endif #define LOG_PRINT(...) do { FILE *printFile = stdout; LOG_TO_FILE(__VA_ARGS__); fprintf(printFile, __VA_ARGS__); fprintf(printFile, "\n"); fflush(printFile); } while(0) #if LOG_ENABLE_DEBUG #define LOG_DEBUG(...) do { FILE *debugFile = stderr; LOG_TO_FILE(__VA_ARGS__); fprintf(debugFile, "[" PROGRAM_NAME "] DEBUG: "); fprintf(debugFile, __VA_ARGS__); fprintf(debugFile, "\n"); fflush(debugFile); } while(0) #else #define LOG_DEBUG(...) #endif #if LOG_ENABLE_ERROR #define LOG_ERROR(...) do { FILE *errorFile = stderr; LOG_TO_FILE(__VA_ARGS__); fprintf(errorFile, "[" PROGRAM_NAME "] ERROR: "); fprintf(errorFile, __VA_ARGS__); fprintf(errorFile, "\n"); fflush(errorFile); } while(0) #else #define LOG_ERROR(...) #endif /* */ static int sigterm_received = FALSE; static int sigint_received = FALSE; /** * Removes the pid file and exits with an appropriate error code. */ static void terminate_gracefully() { if(unlink(PIDFILE_FILENAME) != 0) { LOG_ERROR("Could not delete %s!\n", PIDFILE_FILENAME); exit(5); } else exit(0); } /** * Finds all ntfs-3g file systems by matching all mounted file systems with * the "fusefs" fstypename and reserved1 with ntfs-3g's fuse subtype, and * then unmounts them. */ static void unmount_ntfs3g_filesystems() { LOG_DEBUG("void unmount_ntfs3g_filesystems(): entry"); struct statfs *mntbufp; int mountedFileSystems = getmntinfo(&mntbufp, MNT_WAIT); int i; LOG_DEBUG("Iterating through %d file systems to check for ntfs-3g mounted ones:", mountedFileSystems); for(i = 0; i < mountedFileSystems; ++i) { struct statfs cur_statfs = mntbufp[i]; uint32_t subtype; #if __DARWIN_64_BIT_INO_T subtype = cur_statfs.f_fssubtype; #else subtype = cur_statfs.f_reserved1; #endif /* __DARWIN_64_BIT_INO_T */ if(strncmp(cur_statfs.f_fstypename, FUSEFS_PREFIX, FUSEFS_STRLEN) == 0 && subtype == FUSEFS_NTFS_3G_SUBTYPE) { LOG_DEBUG("Unmounting ntfs-3g file system at: %s", cur_statfs.f_mntonname); int unmountStatus = unmount(cur_statfs.f_mntonname, 0); if(unmountStatus != 0) { // Try to force unmount LOG_ERROR("Could not unmount %s normally, trying to force unmount...", cur_statfs.f_mntonname); unmountStatus = unmount(cur_statfs.f_mntonname, MNT_FORCE); LOG_DEBUG("Force unmount operation completed"); } if(unmountStatus == 0) { LOG_DEBUG("%s was unmounted successfully!", cur_statfs.f_mntonname); } else { LOG_ERROR("Could not unmount %s!", cur_statfs.f_mntonname); } } } LOG_DEBUG("void unmount_ntfs3g_filesystems(): exit"); } static void catch_sigterm(int sig_num) { sigterm_received = TRUE; } static void catch_sigint(int sig_num) { sigint_received = TRUE; } static void sigterm_execute() { LOG_DEBUG("SIGTERM caught!"); unmount_ntfs3g_filesystems(); LOG_DEBUG("Process exiting."); terminate_gracefully(); } static void sigint_execute() { LOG_DEBUG("SIGINT caught!"); terminate_gracefully(); } /** * Unloads a running instance of the program by sending it SIGKILL * and removing its .pid-file. * * Return values: * 0 - Success. * 1 - Could not send SIGKILL to the running process. * 2 - Existing .pid-file could not be deleted. * 3 - A running process could not be found. */ static int unload(void) { FILE *pidFile = fopen(PIDFILE_FILENAME, "r"); if(pidFile != NULL) { pid_t pid; fscanf(pidFile, "%u", &pid); fclose(pidFile); /* Check if any process exists with id pid. */ if(kill(pid, 0) == 0) { /* Process exists. Now try to kill it. */ if(kill(pid, SIGKILL) != 0) { LOG_ERROR("Could not kill process %u!", pid); return 1; } else { if(unlink(PIDFILE_FILENAME) != 0) { LOG_ERROR("Could not delete pid file!"); return 2; } else { LOG_DEBUG("Killed process %u successfully.", pid); return 0; } } } else { LOG_PRINT("Orphaned .pid file found. Removing it."); if(unlink(PIDFILE_FILENAME) != 0) { LOG_ERROR("Could not delete pid file!"); return 2; } return 0; // This is no error condition. } } else { LOG_ERROR("Could not find any running instance."); return 3; } } int main(int argc, char** argv) { LOG_DEBUG("Program started."); if(argc == 1) { /* A .pid-file is placed in /private/var/run to keep track of running * instances. Hopefully people won't spawn a 1000 parallel processes at * one time, as this would obviously break this simple and clean method. */ FILE *pidFile = fopen(PIDFILE_FILENAME, "r"); if(pidFile != NULL) { LOG_PRINT("Unloading existing instance of " PROGRAM_NAME "..."); fclose(pidFile); unload(); } pidFile = fopen(PIDFILE_FILENAME, "w"); if(pidFile != NULL) { LOG_DEBUG("trying to fork"); pid_t pid = fork(); if(pid == 0) { fclose(pidFile); signal(SIGTERM, catch_sigterm); signal(SIGINT, catch_sigint); while(1) { pause(); if(sigterm_received) { sigterm_received = FALSE; sigterm_execute(); } else if(sigint_received) { sigint_received = FALSE; sigint_execute(); } } } else { LOG_DEBUG("Fork complete. process id: %u", pid); fprintf(pidFile, "%u\n", pid); fflush(pidFile); fclose(pidFile); LOG_PRINT(PROGRAM_NAME " started with process id %d.", pid); LOG_DEBUG("Exiting parent process."); return 0; } } else { LOG_PRINT("Could not open .pid-file for writing! (Are you running the program with sufficient privileges?)"); return 2; } } else { if(argc == 2 && strcmp(argv[1], "-u") == 0) { LOG_PRINT("Unloading all running instances of " PROGRAM_NAME "..."); return unload(); } else if(argc == 2 && strcmp(argv[1], "-s") == 0) { LOG_PRINT("Unloading all running instances of " PROGRAM_NAME " and unmounting..."); int res = unload(); if(res != 0) res += 10; unmount_ntfs3g_filesystems(); return res; } else { LOG_PRINT("usage: " PROGRAM_NAME " [-u]"); return 1; } } return 0; }