Index: modules/file-method.c
===================================================================
RCS file: /cvs/gnome/gnome-vfs/modules/file-method.c,v
retrieving revision 1.105.2.1
diff -u -r1.105.2.1 file-method.c
--- modules/file-method.c	13 Jun 2002 19:24:04 -0000	1.105.2.1
+++ modules/file-method.c	3 Jul 2002 20:11:05 -0000
@@ -36,20 +36,24 @@
 #include <libgnomevfs/gnome-vfs-utils.h>
 #include <libgnomevfs/gnome-vfs-mime.h>
 #include <libgnomevfs/gnome-vfs-monitor-private.h>
+#include <libgnomevfs/gnome-vfs-thread-pool.h>
 #include <dirent.h>
 #include <errno.h>
 #include <fcntl.h>
 #include <glib/gstrfuncs.h>
 #include <glib/gutils.h>
 #include <limits.h>
+#include <signal.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <sys/stat.h>
+#include <sys/time.h>
 #include <sys/types.h>
 #include <unistd.h>
 #include <utime.h>
 #include <string.h>
+#include <pthread.h>
 #ifdef HAVE_FAM
 #include <fam.h>
 #include <glib/giochannel.h>
@@ -161,6 +165,233 @@
 	g_free (handle);
 }
 
+#ifndef DISABLE_STATALARM
+#define STAT_TIMEOUT 4L
+
+typedef struct {
+	gchar         *path;
+	pthread_t      thread;
+	struct timeval timeout;
+} StatData;
+
+G_LOCK_DEFINE_STATIC (stat_lock);
+static GCond  *stat_cond = NULL;
+static GSList *stat_threads = NULL;
+static GHashTable *stat_hash = NULL;
+G_LOCK_DEFINE_STATIC (stat_hash);
+
+static void *
+statalarm_thread_start (void *user_data)
+{
+	G_LOCK (stat_lock);
+
+	/* Check all currently active stat()'s and quit */
+	while (stat_threads != NULL) {
+		StatData *data = stat_threads->data;
+		GMutex *wait_mutex;
+		GTimeVal timeout;
+		struct timeval cur_time;
+		gboolean waiting = FALSE, sending = FALSE, timedout = TRUE;
+
+		gettimeofday (&cur_time, NULL);
+
+		if (timercmp (&cur_time, &data->timeout, <)) {
+			waiting = TRUE;
+
+			timeout.tv_sec = data->timeout.tv_sec;
+			timeout.tv_usec = data->timeout.tv_usec;
+
+			wait_mutex = 
+				g_static_mutex_get_mutex (
+					&G_LOCK_NAME(stat_lock));
+
+			/* True = stat_cond was tripped, False = timeout */
+			if (g_cond_timed_wait (stat_cond, 
+					       wait_mutex, 
+					       &timeout)) {
+				timedout = FALSE;
+				continue;
+			}
+		}
+
+		/* Timeout means we send the SIGALRM */
+		if (stat_threads && stat_threads->data == data) {
+			pthread_kill (data->thread, SIGALRM);
+
+			stat_threads = g_slist_remove (stat_threads, data);
+			g_free (data);
+
+			sending = TRUE;
+		}
+	}
+
+	g_cond_free (stat_cond);
+	stat_cond = NULL;
+
+	G_UNLOCK (stat_lock);
+
+	return NULL;
+}
+
+static void
+sigalrm_handler (int signum)
+{
+	/* Do nothing */
+}
+
+static void 
+statalarm_set (void (**alarm_handler) (int), const gchar *path)
+{
+	StatData *data;
+
+	*alarm_handler = signal (SIGALRM, sigalrm_handler);
+	siginterrupt (SIGALRM, 1);
+
+	data = g_new0 (StatData, 1);
+	data->thread = pthread_self ();
+
+	G_LOCK (stat_lock);
+
+	data->path = g_strdup (path);
+
+	/* Signal us STAT_TIMEOUT seconds from now */
+	gettimeofday (&data->timeout, NULL);
+	data->timeout.tv_sec += STAT_TIMEOUT;
+
+	stat_threads = g_slist_append (stat_threads, data);
+
+	if (stat_cond == NULL) {
+		/* Create stat thread */
+		pthread_t thread_id;
+
+		stat_cond = g_cond_new ();
+		gnome_vfs_thread_create (&thread_id, 
+					 statalarm_thread_start,
+					 NULL);
+	} else {
+		/* Wake up stat thread */
+		g_cond_signal (stat_cond);
+	}
+	
+	G_UNLOCK (stat_lock);
+}
+
+static gboolean
+statalarm_clear (void (*alarm_handler) (int))
+{
+	gboolean sigsent = TRUE;
+	GSList *iter;
+	pthread_t self;
+
+	self = pthread_self ();
+
+	G_LOCK (stat_lock);
+
+	for (iter = stat_threads; iter != NULL; iter = iter->next) {
+		StatData *data = iter->data;
+
+		/* Look for this thread's StatData */
+		if (pthread_equal (self, data->thread)) {
+			stat_threads = g_slist_remove (stat_threads, data);
+			g_free (data);
+
+			/* Finding it means no signal was sent */
+			sigsent = FALSE;
+			break;
+		}
+	}
+
+	G_UNLOCK (stat_lock);
+
+	/* Reset previous alarm handler */
+	signal (SIGALRM, alarm_handler);
+
+	return sigsent;
+}
+
+static int
+try_stat_internal (const gchar *filename, 
+		   gboolean     follow_links, 
+		   struct stat *statbuf)
+{
+	struct timeval cur_time;
+	void (*alarm_handler) (int);
+	gpointer cached_filename, laststatp;
+	long laststat;
+	gint retval;
+
+	G_LOCK (stat_hash);
+
+	if (stat_hash == NULL) {
+		stat_hash = g_hash_table_new_full (g_str_hash, 
+						   g_str_equal,
+						   g_free,
+						   NULL);
+	}
+
+	gettimeofday (&cur_time, NULL);
+
+	if (g_hash_table_lookup_extended (stat_hash, 
+					  filename, 
+					  &cached_filename, 
+					  &laststatp)) {
+		laststat = GPOINTER_TO_INT (laststatp);
+
+		if (laststat < cur_time.tv_sec + STAT_TIMEOUT) {
+			G_UNLOCK (stat_hash);
+			errno = EBUSY;
+			return -1;
+		} else {
+			g_hash_table_remove (stat_hash, cached_filename);
+			g_free (cached_filename);
+		}
+	}
+
+	G_UNLOCK (stat_hash);
+
+	statalarm_set (&alarm_handler, filename);
+
+	if (follow_links) 
+		retval = stat (filename, statbuf);
+	else 
+		retval = lstat (filename, statbuf);
+
+	if (statalarm_clear (alarm_handler)) {
+		/* We were cancelled via statalarm thread */
+		if (retval != 0) {
+			G_LOCK (stat_hash);
+			g_hash_table_insert (stat_hash, 
+					     g_strdup (filename), 
+					     GINT_TO_POINTER (cur_time.tv_sec));
+			G_UNLOCK (stat_hash);
+
+			/* Force something a little more accurate */
+			errno = EBUSY;
+		}
+	}
+	
+	return retval;
+}
+
+static int
+try_stat (const gchar *filename, struct stat *statbuf)
+{
+	return try_stat_internal (filename, TRUE, statbuf);
+}
+
+static int
+try_lstat (const gchar *filename, struct stat *statbuf)
+{
+	return try_stat_internal (filename, FALSE, statbuf);
+}
+
+#else  /*DISABLE_STATALARM*/
+#define try_stat  stat
+#define try_lstat lstat
+#define statalarm_set(x)   
+#define statalarm_clear(x) x = NULL
+#endif /*DISABLE_STATALARM*/
+
 static GnomeVFSResult
 do_open (GnomeVFSMethod *method,
 	 GnomeVFSMethodHandle **method_handle,
@@ -196,6 +427,16 @@
 	if (file_name == NULL)
 		return GNOME_VFS_ERROR_INVALID_URI;
 
+	if (try_stat (file_name, &statbuf) != 0) {
+		g_free (file_name);
+		return gnome_vfs_result_from_errno ();
+	}
+
+	if (S_ISDIR (statbuf.st_mode)) {
+		g_free (file_name);
+		return GNOME_VFS_ERROR_IS_DIRECTORY;
+	}
+
 	do
 		fd = OPEN (file_name, unix_mode);
 	while (fd == -1
@@ -207,14 +448,6 @@
 	if (fd == -1)
 		return gnome_vfs_result_from_errno ();
 
-	if (fstat (fd, &statbuf) != 0)
-		return gnome_vfs_result_from_errno ();
-
-	if (S_ISDIR (statbuf.st_mode)) {
-		close (fd);
-		return GNOME_VFS_ERROR_IS_DIRECTORY;
-	}
-
 	file_handle = file_handle_new (uri, fd);
 	
 	*method_handle = (GnomeVFSMethodHandle *) file_handle;
@@ -616,14 +849,14 @@
 		statptr = &statbuf;
 	}
 
-	if (lstat (full_name, statptr) != 0) {
+	if (try_lstat (full_name, statptr) != 0) {
 		return gnome_vfs_result_from_errno ();
 	}
 
 	is_symlink = S_ISLNK (statptr->st_mode);
 
 	if ((options & GNOME_VFS_FILE_INFO_FOLLOW_LINKS) && is_symlink) {
-		if (stat (full_name, statptr) != 0) {
+		if (try_stat (full_name, statptr) != 0) {
 			if (errno == ELOOP) {
 				recursive = TRUE;
 			}
@@ -631,7 +864,7 @@
 			/* It's a broken symlink, revert to the lstat. This is sub-optimal but
 			 * acceptable because it's not a common case.
 			 */
-			if (lstat (full_name, statptr) != 0) {
+			if (try_lstat (full_name, statptr) != 0) {
 				return gnome_vfs_result_from_errno ();
 			}
 		}
@@ -667,7 +900,7 @@
 			                /* if we had an earlier ELOOP, don't get in an infinite loop here */
 			        || recursive
 					/* we don't care to follow links */
-				|| lstat (file_info->symlink_name, statptr) != 0
+				|| try_lstat (symlink_name, statptr) != 0
 					/* we can't make out where this points to */
 				|| !S_ISLNK (statptr->st_mode)) {
 					/* the next level is not a link */
@@ -898,7 +1131,8 @@
 
 	if (local == NULL) {
 		struct stat statbuf;
-		if (stat (path, &statbuf) == 0) {
+		
+		if (try_stat (path, &statbuf) == 0) {
 			char const *type = filesystem_type (path, path, &statbuf);
 			gboolean is_local = strcmp (type, "nfs") && strcmp (type, "afs");
 			local = GINT_TO_POINTER (is_local ? 1 : -1);
@@ -989,7 +1223,7 @@
 		if (dir_separator_scanner - path > 0) {
 			current_path = g_strndup (path, dir_separator_scanner - path);
 			mkdir (current_path, permission_bits);
-			if (stat (current_path, &stat_buffer) != 0) {
+			if (try_stat (current_path, &stat_buffer) != 0) {
 				/* we failed to create a directory and it wasn't there already;
 				 * bail
 				 */
@@ -1047,7 +1281,7 @@
 
 	/* check if there is a trash in this directory */
 	trash_path = append_trash_path (current_directory);
-	if (lstat (trash_path, &stat_buffer) == 0 && S_ISDIR (stat_buffer.st_mode)) {
+	if (try_lstat (trash_path, &stat_buffer) == 0 && S_ISDIR (stat_buffer.st_mode)) {
 		/* found it, we are done */
 		g_assert (near_device_id == stat_buffer.st_dev);
 		return trash_path;
@@ -1081,8 +1315,8 @@
 
 		item_path = append_to_path (current_directory, item->d_name);
 		if (lstat (item_path, &stat_buffer) == 0 
-			&& S_ISDIR (stat_buffer.st_mode)
-			&& near_device_id == stat_buffer.st_dev) {
+		    && S_ISDIR (stat_buffer.st_mode)
+		    && near_device_id == stat_buffer.st_dev) {
 
 			/* Directory -- put it on the list to search, 
 			 * just as long as it is on the same device.
@@ -1200,7 +1434,7 @@
 		}
 		
 		*last_slash = '\0';
-		if (lstat (disk_top_directory, &stat_buffer) < 0
+		if (try_lstat (disk_top_directory, &stat_buffer) < 0
 			|| stat_buffer.st_dev != near_device_id) {
 			/* we ran past the root of the disk we are exploring */
 			g_free (disk_top_directory);
@@ -1394,8 +1628,8 @@
 
 				if (trash_path != NULL 
 					&& mount_point != NULL
-					&& (strcmp (trash_path, NON_EXISTENT_TRASH_ENTRY) == 0 || lstat (trash_path, &stat_buffer) == 0)
-					&& lstat (mount_point, &stat_buffer) == 0) {
+					&& (strcmp (trash_path, NON_EXISTENT_TRASH_ENTRY) == 0 || try_lstat (trash_path, &stat_buffer) == 0)
+					&& try_lstat (mount_point, &stat_buffer) == 0) {
 					/* We either know the trash doesn't exist or we checked that it's really
 					 * there - this is a good entry, copy it into the local cache.
 					 */
@@ -1428,7 +1662,7 @@
 cached_trash_entry_exists (const TrashDirectoryCachedItem *entry)
 {
 	struct stat stat_buffer;
-	return lstat (entry->path, &stat_buffer) == 0;
+	return try_lstat (entry->path, &stat_buffer) == 0;
 }
 
 /* Search through the local cache looking for an entry that matches a given
@@ -1635,7 +1869,7 @@
 		return GNOME_VFS_ERROR_CANCELLED;
 	}
 
-	retval = lstat (full_name_near, &near_item_stat);
+	retval = try_lstat (full_name_near, &near_item_stat);
 	if (retval != 0) {
 		g_free (full_name_near);
 		return gnome_vfs_result_from_errno ();
@@ -1646,7 +1880,7 @@
 		return GNOME_VFS_ERROR_CANCELLED;
 	}
 	
-	retval = stat (home_directory, &home_volume_stat);
+	retval = try_stat (home_directory, &home_volume_stat);
 	if (retval != 0) {
 		g_free (full_name_near);
 		return gnome_vfs_result_from_errno ();
@@ -1730,7 +1964,7 @@
 	struct stat statbuf;
 	gint retval;
 
-	retval = stat (new_full_name, &statbuf);
+	retval = try_stat (new_full_name, &statbuf);
 	if (retval == 0) {
 		/* If we are not allowed to replace an existing file, return an
                    error.  */
@@ -1912,7 +2146,7 @@
 	gint retval;
 
 	full_name_source = get_path_from_uri (source_uri);
-	retval = lstat (full_name_source, &s_source);
+	retval = try_lstat (full_name_source, &s_source);
 	g_free (full_name_source);
 
 	if (retval != 0)
@@ -1922,7 +2156,7 @@
 		return GNOME_VFS_ERROR_CANCELLED;
  
 	full_name_target = get_path_from_uri (target_uri);
-	retval = stat (full_name_target, &s_target);
+	retval = try_stat (full_name_target, &s_target);
 	g_free (full_name_target);
 
 	if (retval != 0)
@@ -2118,14 +2352,27 @@
 	G_LOCK (fam_connection);
 
 	if (fam_connection == NULL) {
+		void (*old_handler) (int);
+		int famret;
+
 		fam_connection = g_malloc0(sizeof(FAMConnection));
-		if (FAMOpen2(fam_connection, "test-monitor") != 0) {
+
+		/* 
+		 * Break into FAMOpen in case FAM is deadlocked waiting for a
+		 * disconnected NFS server.
+		 */
+		statalarm_set (&old_handler, "NONE!");
+		famret = FAMOpen2 (fam_connection, "test-monitor");
+		statalarm_clear (old_handler);
+
+		if (famret != 0) {
 			g_print ("FAMOpen failed, FAMErrno=%d\n", FAMErrno);
-			g_free(fam_connection);
+			g_free (fam_connection);
 			fam_connection = NULL;
 			G_UNLOCK (fam_connection);
 			return FALSE;
-		}
+		}			
+
 		ioc = g_io_channel_unix_new (FAMCONNECTION_GETFD(fam_connection));
 		watch_id = g_io_add_watch (ioc,
 					   G_IO_IN | G_IO_HUP | G_IO_ERR,
@@ -2148,6 +2395,7 @@
 #ifdef HAVE_FAM
 	FileMonitorHandle *handle;
 	char *filename;
+	void (*old_handler) (int);
 
 	if (!monitor_setup ()) {
 		return GNOME_VFS_ERROR_NOT_SUPPORTED;
@@ -2159,17 +2407,28 @@
 	gnome_vfs_uri_ref (uri);
 	filename = get_path_from_uri (uri);
 	
-	if (monitor_type == GNOME_VFS_MONITOR_FILE) {
-		G_LOCK (fam_connection);
-		FAMMonitorFile (fam_connection, filename, 
-			&handle->request, handle);
-		G_UNLOCK (fam_connection);
-	} else {
-		G_LOCK (fam_connection);
-		FAMMonitorDirectory (fam_connection, filename, 
-			&handle->request, handle);
-		G_UNLOCK (fam_connection);
-	}
+	G_LOCK (fam_connection);
+
+	statalarm_set (&old_handler, filename);
+
+	/* 
+	 * Break into FAMMonitor* in case FAM is deadlocked waiting for a
+	 * disconnected NFS server.
+	 */
+	if (monitor_type == GNOME_VFS_MONITOR_FILE)
+		FAMMonitorFile (fam_connection, 
+				filename, 
+				&handle->request, 
+				handle);
+	else
+		FAMMonitorDirectory (fam_connection, 
+				     filename, 
+				     &handle->request, 
+				     handle);
+
+	statalarm_clear (old_handler);
+
+	G_UNLOCK (fam_connection);
 
 	*method_handle_return = (GnomeVFSMethodHandle *)handle;
 
