ftp://ftp.redhat.com/pub/redhat/linux/rawhide/SRPMS/SRPMS/gnome-vfs2-2.3.8-1.src.rpm
[gnome-vfs-httpcaptive.git] / modules / file-method.c
1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
2 /* file-method.c - Local file access method for the GNOME Virtual File
3    System.
4
5    Copyright (C) 1999 Free Software Foundation
6
7    The Gnome Library is free software; you can redistribute it and/or
8    modify it under the terms of the GNU Library General Public License as
9    published by the Free Software Foundation; either version 2 of the
10    License, or (at your option) any later version.
11
12    The Gnome Library is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15    Library General Public License for more details.
16
17    You should have received a copy of the GNU Library General Public
18    License along with the Gnome Library; see the file COPYING.LIB.  If not,
19    write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
20    Boston, MA 02111-1307, USA.
21
22    Authors: 
23         Ettore Perazzoli <ettore@comm2000.it>
24         Pavel Cisler <pavel@eazel.com>
25  */
26
27 #include <config.h>
28
29 #include <libgnomevfs/gnome-vfs-cancellation.h>
30 #include <libgnomevfs/gnome-vfs-context.h>
31 #include <libgnomevfs/gnome-vfs-i18n.h>
32 #include <libgnomevfs/gnome-vfs-method.h>
33 #include <libgnomevfs/gnome-vfs-mime.h>
34 #include <libgnomevfs/gnome-vfs-module-shared.h>
35 #include <libgnomevfs/gnome-vfs-module.h>
36 #include <libgnomevfs/gnome-vfs-utils.h>
37 #include <libgnomevfs/gnome-vfs-mime.h>
38 #include <libgnomevfs/gnome-vfs-monitor-private.h>
39 #include <dirent.h>
40 #include <errno.h>
41 #include <fcntl.h>
42 #include <glib/gstrfuncs.h>
43 #include <glib/gutils.h>
44 #include <limits.h>
45 #include <stdio.h>
46 #include <stdlib.h>
47 #include <string.h>
48 #include <sys/stat.h>
49 #include <sys/types.h>
50 #include <unistd.h>
51 #include <utime.h>
52 #include <string.h>
53 #ifdef HAVE_FAM
54 #include <fam.h>
55 #include <glib/giochannel.h>
56 #endif
57
58 #ifdef HAVE_FAM
59 FAMConnection *fam_connection = NULL;
60 G_LOCK_DEFINE_STATIC (fam_connection);
61
62 typedef struct {
63         FAMRequest request;
64         GnomeVFSURI *uri;
65         gboolean     cancelled;
66 } FileMonitorHandle;
67
68 #endif
69
70 #ifdef PATH_MAX
71 #define GET_PATH_MAX()  PATH_MAX
72 #else
73 static int
74 GET_PATH_MAX (void)
75 {
76         static unsigned int value;
77
78         /* This code is copied from GNU make.  It returns the maximum
79            path length by using `pathconf'.  */
80
81         if (value == 0) {
82                 long int x = pathconf(G_DIR_SEPARATOR_S, _PC_PATH_MAX);
83
84                 if (x > 0)
85                         value = x;
86                 else
87                         return MAXPATHLEN;
88         }
89
90         return value;
91 }
92 #endif
93
94 #ifdef HAVE_OPEN64
95 #define OPEN open64
96 #else
97 #define OPEN open
98 #endif
99
100 #if defined(HAVE_LSEEK64) && defined(HAVE_OFF64_T)
101 #define LSEEK lseek64
102 #define OFF_T off64_t
103 #else
104 #define LSEEK lseek
105 #define OFF_T off_t
106 #endif
107
108 static gchar *
109 get_path_from_uri (GnomeVFSURI const *uri)
110 {
111         gchar *path;
112
113         path = gnome_vfs_unescape_string (uri->text, 
114                 G_DIR_SEPARATOR_S);
115                 
116         if (path == NULL) {
117                 return NULL;
118         }
119
120         if (path[0] != G_DIR_SEPARATOR) {
121                 g_free (path);
122                 return NULL;
123         }
124
125         return path;
126 }
127
128 static gchar *
129 get_base_from_uri (GnomeVFSURI const *uri)
130 {
131         gchar *escaped_base, *base;
132
133         escaped_base = gnome_vfs_uri_extract_short_path_name (uri);
134         base = gnome_vfs_unescape_string (escaped_base, G_DIR_SEPARATOR_S);
135         g_free (escaped_base);
136         return base;
137 }
138
139 typedef struct {
140         GnomeVFSURI *uri;
141         gint fd;
142 } FileHandle;
143
144 static FileHandle *
145 file_handle_new (GnomeVFSURI *uri,
146                  gint fd)
147 {
148         FileHandle *result;
149         result = g_new (FileHandle, 1);
150
151         result->uri = gnome_vfs_uri_ref (uri);
152         result->fd = fd;
153
154         return result;
155 }
156
157 static void
158 file_handle_destroy (FileHandle *handle)
159 {
160         gnome_vfs_uri_unref (handle->uri);
161         g_free (handle);
162 }
163
164 static GnomeVFSResult
165 do_open (GnomeVFSMethod *method,
166          GnomeVFSMethodHandle **method_handle,
167          GnomeVFSURI *uri,
168          GnomeVFSOpenMode mode,
169          GnomeVFSContext *context)
170 {
171         FileHandle *file_handle;
172         gint fd;
173         mode_t unix_mode;
174         gchar *file_name;
175         struct stat statbuf;
176
177         _GNOME_VFS_METHOD_PARAM_CHECK (method_handle != NULL);
178         _GNOME_VFS_METHOD_PARAM_CHECK (uri != NULL);
179
180         if (mode & GNOME_VFS_OPEN_READ) {
181                 if (mode & GNOME_VFS_OPEN_WRITE)
182                         unix_mode = O_RDWR;
183                 else
184                         unix_mode = O_RDONLY;
185         } else {
186                 if (mode & GNOME_VFS_OPEN_WRITE)
187                         unix_mode = O_WRONLY;
188                 else
189                         return GNOME_VFS_ERROR_INVALID_OPEN_MODE;
190         }
191
192         if (! (mode & GNOME_VFS_OPEN_RANDOM) && (mode & GNOME_VFS_OPEN_WRITE))
193                 unix_mode |= O_TRUNC;
194         
195         file_name = get_path_from_uri (uri);
196         if (file_name == NULL)
197                 return GNOME_VFS_ERROR_INVALID_URI;
198
199         do
200                 fd = OPEN (file_name, unix_mode);
201         while (fd == -1
202                && errno == EINTR
203                && ! gnome_vfs_context_check_cancellation (context));
204
205         g_free (file_name);
206
207         if (fd == -1)
208                 return gnome_vfs_result_from_errno ();
209
210         if (fstat (fd, &statbuf) != 0)
211                 return gnome_vfs_result_from_errno ();
212
213         if (S_ISDIR (statbuf.st_mode)) {
214                 close (fd);
215                 return GNOME_VFS_ERROR_IS_DIRECTORY;
216         }
217
218         file_handle = file_handle_new (uri, fd);
219         
220         *method_handle = (GnomeVFSMethodHandle *) file_handle;
221
222         return GNOME_VFS_OK;
223 }
224
225 static GnomeVFSResult
226 do_create (GnomeVFSMethod *method,
227            GnomeVFSMethodHandle **method_handle,
228            GnomeVFSURI *uri,
229            GnomeVFSOpenMode mode,
230            gboolean exclusive,
231            guint perm,
232            GnomeVFSContext *context)
233 {
234         FileHandle *file_handle;
235         gint fd;
236         mode_t unix_mode;
237         gchar *file_name;
238
239         _GNOME_VFS_METHOD_PARAM_CHECK (method_handle != NULL);
240         _GNOME_VFS_METHOD_PARAM_CHECK (uri != NULL);
241
242         unix_mode = O_CREAT | O_TRUNC;
243         
244         if (!(mode & GNOME_VFS_OPEN_WRITE))
245                 return GNOME_VFS_ERROR_INVALID_OPEN_MODE;
246
247         if (mode & GNOME_VFS_OPEN_READ)
248                 unix_mode |= O_RDWR;
249         else
250                 unix_mode |= O_WRONLY;
251
252         if (exclusive)
253                 unix_mode |= O_EXCL;
254
255         file_name = get_path_from_uri (uri);
256         if (file_name == NULL)
257                 return GNOME_VFS_ERROR_INVALID_URI;
258
259         do
260                 fd = OPEN (file_name, unix_mode, perm);
261         while (fd == -1
262                && errno == EINTR
263                && ! gnome_vfs_context_check_cancellation (context));
264
265         g_free (file_name);
266
267         if (fd == -1)
268                 return gnome_vfs_result_from_errno ();
269
270         file_handle = file_handle_new (uri, fd);
271
272         *method_handle = (GnomeVFSMethodHandle *) file_handle;
273
274         return GNOME_VFS_OK;
275 }
276
277 static GnomeVFSResult
278 do_close (GnomeVFSMethod *method,
279           GnomeVFSMethodHandle *method_handle,
280           GnomeVFSContext *context)
281 {
282         FileHandle *file_handle;
283         gint close_retval;
284
285         g_return_val_if_fail (method_handle != NULL, GNOME_VFS_ERROR_INTERNAL);
286
287         file_handle = (FileHandle *) method_handle;
288
289         do
290                 close_retval = close (file_handle->fd);
291         while (close_retval != 0
292                && errno == EINTR
293                && ! gnome_vfs_context_check_cancellation (context));
294
295         /* FIXME bugzilla.eazel.com 1163: Should do this even after a failure?  */
296         file_handle_destroy (file_handle);
297
298         if (close_retval != 0) {
299                 return gnome_vfs_result_from_errno ();
300         }
301
302         return GNOME_VFS_OK;
303 }
304
305 static GnomeVFSResult
306 do_read (GnomeVFSMethod *method,
307          GnomeVFSMethodHandle *method_handle,
308          gpointer buffer,
309          GnomeVFSFileSize num_bytes,
310          GnomeVFSFileSize *bytes_read,
311          GnomeVFSContext *context)
312 {
313         FileHandle *file_handle;
314         gint read_val;
315
316         g_return_val_if_fail (method_handle != NULL, GNOME_VFS_ERROR_INTERNAL);
317
318         file_handle = (FileHandle *) method_handle;
319
320         do {
321                 read_val = read (file_handle->fd, buffer, num_bytes);
322         } while (read_val == -1
323                  && errno == EINTR
324                  && ! gnome_vfs_context_check_cancellation (context));
325
326         if (read_val == -1) {
327                 *bytes_read = 0;
328                 return gnome_vfs_result_from_errno ();
329         } else {
330                 *bytes_read = read_val;
331
332                 /* Getting 0 from read() means EOF! */
333                 if (read_val == 0) {
334                         return GNOME_VFS_ERROR_EOF;
335                 }
336         }
337         return GNOME_VFS_OK;
338 }
339
340 static GnomeVFSResult
341 do_write (GnomeVFSMethod *method,
342           GnomeVFSMethodHandle *method_handle,
343           gconstpointer buffer,
344           GnomeVFSFileSize num_bytes,
345           GnomeVFSFileSize *bytes_written,
346           GnomeVFSContext *context)
347 {
348         FileHandle *file_handle;
349         gint write_val;
350
351         g_return_val_if_fail (method_handle != NULL, GNOME_VFS_ERROR_INTERNAL);
352
353         file_handle = (FileHandle *) method_handle;
354
355         do
356                 write_val = write (file_handle->fd, buffer, num_bytes);
357         while (write_val == -1
358                && errno == EINTR
359                && ! gnome_vfs_context_check_cancellation (context));
360
361         if (write_val == -1) {
362                 *bytes_written = 0;
363                 return gnome_vfs_result_from_errno ();
364         } else {
365                 *bytes_written = write_val;
366                 return GNOME_VFS_OK;
367         }
368 }
369
370
371 static gint
372 seek_position_to_unix (GnomeVFSSeekPosition position)
373 {
374         switch (position) {
375         case GNOME_VFS_SEEK_START:
376                 return SEEK_SET;
377         case GNOME_VFS_SEEK_CURRENT:
378                 return SEEK_CUR;
379         case GNOME_VFS_SEEK_END:
380                 return SEEK_END;
381         default:
382                 g_warning (_("Unknown GnomeVFSSeekPosition %d"), position);
383                 return SEEK_SET; /* bogus */
384         }
385 }
386
387 static GnomeVFSResult
388 do_seek (GnomeVFSMethod *method,
389          GnomeVFSMethodHandle *method_handle,
390          GnomeVFSSeekPosition whence,
391          GnomeVFSFileOffset offset,
392          GnomeVFSContext *context)
393 {
394         FileHandle *file_handle;
395         gint lseek_whence;
396
397         file_handle = (FileHandle *) method_handle;
398         lseek_whence = seek_position_to_unix (whence);
399
400         if (LSEEK (file_handle->fd, offset, lseek_whence) == -1) {
401                 if (errno == ESPIPE)
402                         return GNOME_VFS_ERROR_NOT_SUPPORTED;
403                 else
404                         return gnome_vfs_result_from_errno ();
405         }
406
407         return GNOME_VFS_OK;
408 }
409
410 static GnomeVFSResult
411 do_tell (GnomeVFSMethod *method,
412          GnomeVFSMethodHandle *method_handle,
413          GnomeVFSFileOffset *offset_return)
414 {
415         FileHandle *file_handle;
416         OFF_T offset;
417
418         file_handle = (FileHandle *) method_handle;
419
420         offset = LSEEK (file_handle->fd, 0, SEEK_CUR);
421         if (offset == -1) {
422                 if (errno == ESPIPE)
423                         return GNOME_VFS_ERROR_NOT_SUPPORTED;
424                 else
425                         return gnome_vfs_result_from_errno ();
426         }
427
428         *offset_return = offset;
429         return GNOME_VFS_OK;
430 }
431
432
433 static GnomeVFSResult
434 do_truncate_handle (GnomeVFSMethod *method,
435                     GnomeVFSMethodHandle *method_handle,
436                     GnomeVFSFileSize where,
437                     GnomeVFSContext *context)
438 {
439         FileHandle *file_handle;
440
441         g_return_val_if_fail (method_handle != NULL, GNOME_VFS_ERROR_INTERNAL);
442
443         file_handle = (FileHandle *) method_handle;
444
445         if (ftruncate (file_handle->fd, where) == 0) {
446                 return GNOME_VFS_OK;
447         } else {
448                 switch (errno) {
449                 case EBADF:
450                 case EROFS:
451                         return GNOME_VFS_ERROR_READ_ONLY;
452                 case EINVAL:
453                         return GNOME_VFS_ERROR_NOT_SUPPORTED;
454                 default:
455                         return GNOME_VFS_ERROR_GENERIC;
456                 }
457         }
458 }
459
460 static GnomeVFSResult
461 do_truncate (GnomeVFSMethod *method,
462              GnomeVFSURI *uri,
463              GnomeVFSFileSize where,
464              GnomeVFSContext *context)
465 {
466         gchar *path;
467
468         path = get_path_from_uri (uri);
469         if (path == NULL)
470                 return GNOME_VFS_ERROR_INVALID_URI;
471
472         if (truncate (path, where) == 0) {
473                 g_free (path);
474                 return GNOME_VFS_OK;
475         } else {
476                 g_free (path);
477                 switch (errno) {
478                 case EBADF:
479                 case EROFS:
480                         return GNOME_VFS_ERROR_READ_ONLY;
481                 case EINVAL:
482                         return GNOME_VFS_ERROR_NOT_SUPPORTED;
483                 default:
484                         return GNOME_VFS_ERROR_GENERIC;
485                 }
486         }
487 }
488
489 typedef struct {
490         GnomeVFSURI *uri;
491         DIR *dir;
492         GnomeVFSFileInfoOptions options;
493
494         struct dirent *current_entry;
495
496         gchar *name_buffer;
497         gchar *name_ptr;
498 } DirectoryHandle;
499
500 static DirectoryHandle *
501 directory_handle_new (GnomeVFSURI *uri,
502                       DIR *dir,
503                       GnomeVFSFileInfoOptions options)
504 {
505         DirectoryHandle *result;
506         gchar *full_name;
507         guint full_name_len;
508
509         result = g_new (DirectoryHandle, 1);
510
511         result->uri = gnome_vfs_uri_ref (uri);
512         result->dir = dir;
513
514         /* Reserve extra space for readdir_r, see man page */
515         result->current_entry = g_malloc (sizeof (struct dirent) + GET_PATH_MAX() + 1);
516
517         full_name = get_path_from_uri (uri);
518         g_assert (full_name != NULL); /* already done by caller */
519         full_name_len = strlen (full_name);
520
521         result->name_buffer = g_malloc (full_name_len + GET_PATH_MAX () + 2);
522         memcpy (result->name_buffer, full_name, full_name_len);
523         
524         if (full_name_len > 0 && full_name[full_name_len - 1] != '/')
525                 result->name_buffer[full_name_len++] = '/';
526
527         result->name_ptr = result->name_buffer + full_name_len;
528
529         g_free (full_name);
530
531         result->options = options;
532
533         return result;
534 }
535
536 static void
537 directory_handle_destroy (DirectoryHandle *directory_handle)
538 {
539         gnome_vfs_uri_unref (directory_handle->uri);
540         g_free (directory_handle->name_buffer);
541         g_free (directory_handle->current_entry);
542         g_free (directory_handle);
543 }
544
545 /* MIME detection code.  */
546 static void
547 get_mime_type (GnomeVFSFileInfo *info,
548                const char *full_name,
549                GnomeVFSFileInfoOptions options,
550                struct stat *stat_buffer)
551 {
552         const char *mime_type;
553
554         mime_type = NULL;
555         if ((options & GNOME_VFS_FILE_INFO_FOLLOW_LINKS) == 0
556                 && (info->type == GNOME_VFS_FILE_TYPE_SYMBOLIC_LINK)) {
557                 /* we are a symlink and aren't asked to follow -
558                  * return the type for a symlink
559                  */
560                 mime_type = "x-special/symlink";
561         } else {
562                 mime_type = gnome_vfs_get_file_mime_type (full_name,
563                         stat_buffer, (options & GNOME_VFS_FILE_INFO_FORCE_FAST_MIME_TYPE) != 0);
564         }
565
566         g_assert (mime_type);
567         info->mime_type = g_strdup (mime_type);
568         info->valid_fields |= GNOME_VFS_FILE_INFO_FIELDS_MIME_TYPE;
569 }
570
571 static gchar *
572 read_link (const gchar *full_name)
573 {
574         gchar *buffer;
575         guint size;
576
577         size = 256;
578         buffer = g_malloc (size);
579           
580         while (1) {
581                 int read_size;
582
583                 read_size = readlink (full_name, buffer, size);
584                 if (read_size < 0) {
585                         g_free (buffer);
586                         return NULL;
587                 }
588                 if (read_size < size) {
589                         buffer[read_size] = 0;
590                         return buffer;
591                 }
592                 size *= 2;
593                 buffer = g_realloc (buffer, size);
594         }
595 }
596
597 static void
598 get_access_info (GnomeVFSFileInfo *file_info,
599               const gchar *full_name)
600 {
601      /* FIXME: should check errno after calling access because we don't
602       * want to set valid_fields if something bad happened during one
603       * of the access calls
604       */
605      if (access (full_name, R_OK) == 0) {
606              file_info->permissions |= GNOME_VFS_PERM_ACCESS_READABLE;
607      }
608
609      if (access (full_name, W_OK) == 0) {
610              file_info->permissions |= GNOME_VFS_PERM_ACCESS_WRITABLE;
611      }
612
613      if (access (full_name, X_OK) == 0) {
614              file_info->permissions |= GNOME_VFS_PERM_ACCESS_EXECUTABLE;
615      }
616      file_info->valid_fields |= GNOME_VFS_FILE_INFO_FIELDS_ACCESS;
617 }
618
619 static GnomeVFSResult
620 get_stat_info (GnomeVFSFileInfo *file_info,
621                const gchar *full_name,
622                GnomeVFSFileInfoOptions options,
623                struct stat *statptr)
624 {
625         struct stat statbuf;
626         gboolean followed_symlink;
627         gboolean is_symlink;
628         gboolean recursive;
629         char *link_file_path;
630         char *symlink_name;
631         char *symlink_dir;
632         char *newpath;
633         
634         followed_symlink = FALSE;
635         
636         recursive = FALSE;
637
638         GNOME_VFS_FILE_INFO_SET_LOCAL (file_info, TRUE);
639
640         if (statptr == NULL) {
641                 statptr = &statbuf;
642         }
643
644         if (lstat (full_name, statptr) != 0) {
645                 return gnome_vfs_result_from_errno ();
646         }
647
648         is_symlink = S_ISLNK (statptr->st_mode);
649
650         if ((options & GNOME_VFS_FILE_INFO_FOLLOW_LINKS) && is_symlink) {
651                 if (stat (full_name, statptr) != 0) {
652                         if (errno == ELOOP) {
653                                 recursive = TRUE;
654                         }
655
656                         /* It's a broken symlink, revert to the lstat. This is sub-optimal but
657                          * acceptable because it's not a common case.
658                          */
659                         if (lstat (full_name, statptr) != 0) {
660                                 return gnome_vfs_result_from_errno ();
661                         }
662                 }
663                 GNOME_VFS_FILE_INFO_SET_SYMLINK (file_info, TRUE);
664                 followed_symlink = TRUE;
665         }
666
667         gnome_vfs_stat_to_file_info (file_info, statptr);
668
669         if (is_symlink) {
670                 symlink_name = NULL;
671                 link_file_path = g_strdup (full_name);
672                 
673                 /* We will either successfully read the link name or return
674                  * NULL if read_link fails -- flag it as a valid field either
675                  * way.
676                  */
677                 file_info->valid_fields |= GNOME_VFS_FILE_INFO_FIELDS_SYMLINK_NAME;
678
679                 while (TRUE) {                  
680                         /* Deal with multiple-level symlinks by following them as
681                          * far as we can.
682                          */
683
684                         g_free (symlink_name);
685                         symlink_name = read_link (link_file_path);
686                         if (symlink_name == NULL) {
687                                 g_free (link_file_path);
688                                 return gnome_vfs_result_from_errno ();
689                         }
690                         if (symlink_name[0] != '/') {
691                                 symlink_dir = g_path_get_dirname (link_file_path);
692                                 newpath = g_build_filename (symlink_dir,
693                                                             symlink_name, NULL);
694                                 g_free (symlink_dir);
695                                 g_free (symlink_name);
696                                 symlink_name = newpath;
697                         }
698                         
699                         if ((options & GNOME_VFS_FILE_INFO_FOLLOW_LINKS) == 0
700                                         /* if we had an earlier ELOOP, don't get in an infinite loop here */
701                                 || recursive
702                                         /* we don't care to follow links */
703                                 || lstat (symlink_name, statptr) != 0
704                                         /* we can't make out where this points to */
705                                 || !S_ISLNK (statptr->st_mode)) {
706                                         /* the next level is not a link */
707                                 break;
708                         }
709                         g_free (link_file_path);
710                         link_file_path = g_strdup (symlink_name);
711                 }
712                 g_free (link_file_path);
713
714                 file_info->symlink_name = symlink_name;
715         }
716
717         return GNOME_VFS_OK;
718 }
719
720 static GnomeVFSResult
721 get_stat_info_from_handle (GnomeVFSFileInfo *file_info,
722                            FileHandle *handle,
723                            GnomeVFSFileInfoOptions options,
724                            struct stat *statptr)
725 {
726         struct stat statbuf;
727
728         if (statptr == NULL) {
729                 statptr = &statbuf;
730         }
731
732         if (fstat (handle->fd, statptr) != 0) {
733                 return gnome_vfs_result_from_errno ();
734         }
735         
736         gnome_vfs_stat_to_file_info (file_info, statptr);
737         GNOME_VFS_FILE_INFO_SET_LOCAL (file_info, TRUE);
738
739         return GNOME_VFS_OK;
740 }
741
742
743 static GnomeVFSResult
744 do_open_directory (GnomeVFSMethod *method,
745                    GnomeVFSMethodHandle **method_handle,
746                    GnomeVFSURI *uri,
747                    GnomeVFSFileInfoOptions options,
748                    GnomeVFSContext *context)
749 {
750         gchar *directory_name;
751         DIR *dir;
752
753         directory_name = get_path_from_uri (uri);
754         if (directory_name == NULL)
755                 return GNOME_VFS_ERROR_INVALID_URI;
756
757         dir = opendir (directory_name);
758         g_free (directory_name);
759         if (dir == NULL)
760                 return gnome_vfs_result_from_errno ();
761
762         *method_handle
763                 = (GnomeVFSMethodHandle *) directory_handle_new (uri, dir,
764                                                                  options);
765
766         return GNOME_VFS_OK;
767 }
768
769 static GnomeVFSResult
770 do_close_directory (GnomeVFSMethod *method,
771                     GnomeVFSMethodHandle *method_handle,
772                     GnomeVFSContext *context)
773 {
774         DirectoryHandle *directory_handle;
775
776         directory_handle = (DirectoryHandle *) method_handle;
777
778         closedir (directory_handle->dir);
779
780         directory_handle_destroy (directory_handle);
781
782         return GNOME_VFS_OK;
783 }
784
785 #ifndef HAVE_READDIR_R
786 G_LOCK_DEFINE_STATIC (readdir);
787 #endif
788
789 static GnomeVFSResult
790 do_read_directory (GnomeVFSMethod *method,
791                    GnomeVFSMethodHandle *method_handle,
792                    GnomeVFSFileInfo *file_info,
793                    GnomeVFSContext *context)
794 {
795         struct dirent *result;
796         struct stat statbuf;
797         gchar *full_name;
798         DirectoryHandle *handle;
799
800         handle = (DirectoryHandle *) method_handle;
801         
802         errno = 0;
803 #ifdef HAVE_READDIR_R   
804         if (readdir_r (handle->dir, handle->current_entry, &result) != 0) {
805                 /* Work around a Solaris bug.
806                  * readdir64_r returns -1 instead of 0 at EOF.
807                  */
808                 if (errno == 0) {
809                         return GNOME_VFS_ERROR_EOF;
810                 }
811                 return gnome_vfs_result_from_errno ();
812         }
813 #else
814         G_LOCK (readdir);
815         errno = 0;
816         result = readdir (handle->dir);
817
818         if (result == NULL && errno != 0) {
819                 GnomeVFSResult ret = gnome_vfs_result_from_errno ();
820                 G_UNLOCK (readdir);
821                 return ret;
822         }
823         if (result != NULL) {
824                 memcpy (handle->current_entry, result, sizeof (struct dirent));
825         }
826         G_UNLOCK (readdir);
827 #endif
828         
829         if (result == NULL) {
830                 return GNOME_VFS_ERROR_EOF;
831         }
832
833         file_info->name = g_strdup (result->d_name);
834
835         strcpy (handle->name_ptr, result->d_name);
836         full_name = handle->name_buffer;
837
838         if (get_stat_info (file_info, full_name, handle->options, &statbuf) != GNOME_VFS_OK) {
839                 /* Return OK - this should not terminate the directory iteration
840                  * and we will know from the valid_fields that we don't have the
841                  * stat info.
842                  */
843                 return GNOME_VFS_OK;
844         }
845         
846         if (handle->options & GNOME_VFS_FILE_INFO_GET_MIME_TYPE) {
847                 get_mime_type (file_info, full_name, handle->options, &statbuf);
848         }
849
850         return GNOME_VFS_OK;
851 }
852
853 static GnomeVFSResult
854 do_get_file_info (GnomeVFSMethod *method,
855                   GnomeVFSURI *uri,
856                   GnomeVFSFileInfo *file_info,
857                   GnomeVFSFileInfoOptions options,
858                   GnomeVFSContext *context)
859 {
860         GnomeVFSResult result;
861         gchar *full_name;
862         struct stat statbuf;
863
864         full_name = get_path_from_uri (uri);
865         if (full_name == NULL)
866                 return GNOME_VFS_ERROR_INVALID_URI;
867
868         file_info->valid_fields = GNOME_VFS_FILE_INFO_FIELDS_NONE;
869
870         file_info->name = get_base_from_uri (uri);
871         g_assert (file_info->name != NULL);
872
873         result = get_stat_info (file_info, full_name, options, &statbuf);
874         if (result != GNOME_VFS_OK) {
875                 g_free (full_name);
876                 return result;
877         }
878
879         if (options & GNOME_VFS_FILE_INFO_GET_ACCESS_RIGHTS) {
880                 get_access_info (file_info, full_name);
881         }
882
883         if (options & GNOME_VFS_FILE_INFO_GET_MIME_TYPE) {
884                 get_mime_type (file_info, full_name, options, &statbuf);
885         }
886
887         g_free (full_name);
888
889         return GNOME_VFS_OK;
890 }
891
892 static GnomeVFSResult
893 do_get_file_info_from_handle (GnomeVFSMethod *method,
894                               GnomeVFSMethodHandle *method_handle,
895                               GnomeVFSFileInfo *file_info,
896                               GnomeVFSFileInfoOptions options,
897                               GnomeVFSContext *context)
898 {
899         FileHandle *file_handle;
900         gchar *full_name;
901         struct stat statbuf;
902         GnomeVFSResult result;
903
904         file_handle = (FileHandle *) method_handle;
905
906         file_info->valid_fields = GNOME_VFS_FILE_INFO_FIELDS_NONE;
907
908         full_name = get_path_from_uri (file_handle->uri);
909         if (full_name == NULL) {
910                 return GNOME_VFS_ERROR_INVALID_URI;
911         }
912
913         file_info->name = get_base_from_uri (file_handle->uri);
914         g_assert (file_info->name != NULL);
915
916         result = get_stat_info_from_handle (file_info, file_handle,
917                                             options, &statbuf);
918         if (result != GNOME_VFS_OK) {
919                 g_free (full_name);
920                 return result;
921         }
922
923         if (options & GNOME_VFS_FILE_INFO_GET_MIME_TYPE) {
924                 get_mime_type (file_info, full_name, options, &statbuf);
925         }
926
927         g_free (full_name);
928
929         return GNOME_VFS_OK;
930 }
931
932 GHashTable *fstype_hash = NULL;
933 G_LOCK_DEFINE_STATIC (fstype_hash);
934 extern char *filesystem_type (char *path, char *relpath, struct stat *statp);
935
936 static gboolean
937 do_is_local (GnomeVFSMethod *method,
938              const GnomeVFSURI *uri)
939 {
940         gchar *path;
941         gpointer local = NULL;
942
943         g_return_val_if_fail (uri != NULL, FALSE);
944
945         path = get_path_from_uri (uri);
946         if (path == NULL)
947                 return TRUE; /* GNOME_VFS_ERROR_INVALID_URI */
948
949         G_LOCK (fstype_hash);
950         if (fstype_hash == NULL)
951                 fstype_hash = g_hash_table_new_full (
952                         g_str_hash, g_str_equal, g_free, NULL);
953         else
954                 local = g_hash_table_lookup (fstype_hash, path);
955
956         if (local == NULL) {
957                 struct stat statbuf;
958                 if (stat (path, &statbuf) == 0) {
959                         char *type = filesystem_type (path, path, &statbuf);
960                         gboolean is_local = ((strcmp (type, "nfs") != 0) && 
961                                              (strcmp (type, "afs") != 0) &&
962                                              (strcmp (type, "ncpfs") != 0));
963                         local = GINT_TO_POINTER (is_local ? 1 : -1);
964                         g_hash_table_insert (fstype_hash, path, local);
965                 }
966         } else
967                 g_free (path);
968
969         G_UNLOCK (fstype_hash);
970         return GPOINTER_TO_INT (local) > 0;
971 }
972
973
974 static GnomeVFSResult
975 do_make_directory (GnomeVFSMethod *method,
976                    GnomeVFSURI *uri,
977                    guint perm,
978                    GnomeVFSContext *context)
979 {
980         gint retval;
981         gchar *full_name;
982
983         full_name = get_path_from_uri (uri);
984         if (full_name == NULL)
985                 return GNOME_VFS_ERROR_INVALID_URI;
986
987         retval = mkdir (full_name, perm);
988
989         g_free (full_name);
990
991         if (retval != 0) {
992                 return gnome_vfs_result_from_errno ();
993         }
994
995         return GNOME_VFS_OK;
996 }
997
998 static GnomeVFSResult
999 do_remove_directory (GnomeVFSMethod *method,
1000                      GnomeVFSURI *uri,
1001                      GnomeVFSContext *context)
1002 {
1003         gchar *full_name;
1004         gint retval;
1005
1006         full_name = get_path_from_uri (uri);
1007         if (full_name == NULL)
1008                 return GNOME_VFS_ERROR_INVALID_URI;
1009
1010         retval = rmdir (full_name);
1011
1012         g_free (full_name);
1013
1014         if (retval != 0) {
1015                 return gnome_vfs_result_from_errno ();
1016         }
1017
1018         return GNOME_VFS_OK;
1019 }
1020
1021 #undef DEBUG_FIND_DIRECTORY
1022 /* Get rid of debugging code once we know the logic works. */
1023
1024 #define TRASH_DIRECTORY_NAME_BASE ".Trash"
1025 #define MAX_TRASH_SEARCH_DEPTH 5
1026
1027 /* mkdir_recursive 
1028  * Works like mkdir, except it creates all the levels of directories in @path.
1029  */
1030 static int
1031 mkdir_recursive (const char *path, int permission_bits)
1032 {
1033         struct stat stat_buffer;
1034         const char *dir_separator_scanner;
1035         char *current_path;
1036
1037         /* try creating a director for each level */
1038         for (dir_separator_scanner = path;; dir_separator_scanner++) {
1039                 /* advance to the next directory level */
1040                 for (;;dir_separator_scanner++) {
1041                         if (!*dir_separator_scanner) {
1042                                 break;
1043                         }       
1044                         if (*dir_separator_scanner == G_DIR_SEPARATOR) {
1045                                 break;
1046                         }
1047                 }
1048                 if (dir_separator_scanner - path > 0) {
1049                         current_path = g_strndup (path, dir_separator_scanner - path);
1050                         mkdir (current_path, permission_bits);
1051                         if (stat (current_path, &stat_buffer) != 0) {
1052                                 /* we failed to create a directory and it wasn't there already;
1053                                  * bail
1054                                  */
1055                                 g_free (current_path);
1056                                 return -1;
1057                         }
1058                         g_free (current_path);
1059                 }
1060                 if (!*dir_separator_scanner) {
1061                         break;
1062                 }       
1063         }
1064         return 0;
1065 }
1066
1067
1068 static char *
1069 append_to_path (const char *path, const char *name)
1070 {
1071         return g_strconcat (path, G_DIR_SEPARATOR_S, name, NULL);
1072 }
1073
1074 static char *
1075 append_trash_path (const char *path)
1076 {       
1077         /* When creating trash outside of /home/pavel, create it in the form:
1078          * .Trash-pavel to allow sharing the name space for several users.
1079          * Treat "/" specially to avoid creating non-canonical "//foo" path.
1080          */
1081         if (strcmp (path, "/") == 0) {
1082                 return g_strconcat (path, TRASH_DIRECTORY_NAME_BASE,
1083                 "-", g_get_user_name (), NULL);
1084         } else {
1085                 return g_strconcat (path, G_DIR_SEPARATOR_S, TRASH_DIRECTORY_NAME_BASE,
1086                         "-", g_get_user_name (), NULL);
1087         }
1088 }
1089
1090 /* Try to find the Trash in @current_directory. If not found, collect all the 
1091  * directories in @current_directory to visit later.
1092  */
1093 static char *
1094 find_trash_in_one_hierarchy_level (const char *current_directory, dev_t near_device_id, 
1095         GList **directory_list, GnomeVFSContext *context)
1096 {
1097         char *trash_path;
1098         char *item_path;
1099         struct stat stat_buffer;
1100         DIR *directory;
1101         struct dirent *item_buffer;
1102         struct dirent *item;
1103
1104         if (gnome_vfs_context_check_cancellation (context))
1105                 return NULL;
1106
1107         /* check if there is a trash in this directory */
1108         trash_path = append_trash_path (current_directory);
1109         if (lstat (trash_path, &stat_buffer) == 0 && S_ISDIR (stat_buffer.st_mode)) {
1110                 /* found it, we are done */
1111                 g_assert (near_device_id == stat_buffer.st_dev);
1112                 return trash_path;
1113         }
1114         g_free (trash_path);
1115
1116
1117         if (gnome_vfs_context_check_cancellation (context))
1118                 return NULL;
1119
1120         /* Trash not in this directory.
1121          * Collect the list of all the directories in this directory to visit later.
1122          */
1123         directory = opendir (current_directory);
1124         if (directory == NULL) {
1125                 return NULL;
1126         }
1127
1128         item_buffer = g_malloc (sizeof (struct dirent) + GET_PATH_MAX() + 1);
1129         for (;;) {
1130 #ifdef HAVE_READDIR_R
1131                 if (readdir_r (directory, item_buffer, &item) != 0 || item == NULL) {
1132                         break;
1133                 }
1134 #else
1135                 G_LOCK (readdir);
1136                 item = readdir (directory);
1137                 if (item == NULL) {
1138                         G_UNLOCK (readdir);
1139                         break;
1140                 }
1141 #endif
1142
1143                 if (gnome_vfs_context_check_cancellation (context)) {
1144 #ifndef HAVE_READDIR_R
1145                         G_UNLOCK (readdir);
1146 #endif
1147                         break;
1148                 }
1149
1150                 if (strcmp (item->d_name, ".") == 0
1151                         || strcmp (item->d_name, "..") == 0) {
1152 #ifndef HAVE_READDIR_R
1153                         G_UNLOCK (readdir);
1154 #endif
1155                         continue;
1156                 }
1157
1158                 item_path = append_to_path (current_directory, item->d_name);
1159 #ifndef HAVE_READDIR_R
1160                 G_UNLOCK (readdir);
1161 #endif
1162                 if (lstat (item_path, &stat_buffer) == 0 
1163                         && S_ISDIR (stat_buffer.st_mode)
1164                         && near_device_id == stat_buffer.st_dev) {
1165
1166                         /* Directory -- put it on the list to search, 
1167                          * just as long as it is on the same device.
1168                          */
1169                         *directory_list = g_list_prepend (*directory_list, item_path);
1170                 } else {
1171                         g_free (item_path);
1172                 }
1173                 if (gnome_vfs_context_check_cancellation (context))
1174                         break;
1175         }
1176
1177
1178         closedir (directory);
1179         g_free (item_buffer);
1180         return NULL;
1181 }
1182
1183 /* Do a width-first search of the directory hierarchy starting at start_dir,
1184  * looking for the trash directory. 
1185  * Not doing a traditional depth-first search here to prevent descending too deep in
1186  * the hierarchy -- we expect the Trash to be in a reasonably "shallow" location.
1187  * 
1188  * We only look MAX_TRASH_SEARCH_DEPTH deep, if the Trash is deeper in the hierarchy,
1189  * we will fail to find it.
1190  */
1191 static char *
1192 find_trash_in_hierarchy (const char *start_dir, dev_t near_device_id, GnomeVFSContext *context)
1193 {
1194         GList *next_directory_list;
1195         char *result;
1196
1197 #ifdef DEBUG_FIND_DIRECTORY
1198         g_print ("searching for trash in %s\n", start_dir);
1199 #endif
1200
1201         next_directory_list = NULL;
1202
1203         /* Search the top level. */
1204         result = find_trash_in_one_hierarchy_level (start_dir, near_device_id, 
1205                 &next_directory_list, context);
1206         
1207         gnome_vfs_list_deep_free (next_directory_list);
1208
1209         return result;
1210 }
1211
1212 static GList *cached_trash_directories;
1213 G_LOCK_DEFINE_STATIC (cached_trash_directories);
1214
1215 /* Element used to store chached Trash entries in the local, in-memory Trash item cache. */
1216 typedef struct {
1217         char *path;
1218         char *device_mount_point;
1219         dev_t device_id;
1220 } TrashDirectoryCachedItem;
1221
1222 typedef struct {
1223         dev_t device_id;
1224 } FindByDeviceIDParameters;
1225
1226 static int
1227 match_trash_item_by_device_id (gconstpointer item, gconstpointer data)
1228 {
1229         const TrashDirectoryCachedItem *cached_item;
1230         FindByDeviceIDParameters *parameters;
1231
1232         cached_item = (const TrashDirectoryCachedItem *)item;
1233         parameters = (FindByDeviceIDParameters *)data;
1234         
1235         return cached_item->device_id == parameters->device_id ? 0 : -1;
1236 }
1237
1238 static char *
1239 try_creating_trash_in (const char *path, guint permissions)
1240 {
1241         char *trash_path;
1242
1243
1244         trash_path = append_trash_path (path);
1245         if (mkdir_recursive (trash_path, permissions) == 0) {
1246 #ifdef DEBUG_FIND_DIRECTORY
1247                 g_print ("created trash in %s\n", trash_path);
1248 #endif
1249                 return trash_path;
1250         }
1251
1252 #ifdef DEBUG_FIND_DIRECTORY
1253         g_print ("failed to create trash in %s\n", trash_path);
1254 #endif
1255         g_free (trash_path);
1256         return NULL;
1257 }
1258
1259 static char *
1260 find_disk_top_directory (const char *item_on_disk,
1261                          dev_t near_device_id,
1262                          GnomeVFSContext *context)
1263 {
1264         char *disk_top_directory;
1265         struct stat stat_buffer;
1266
1267         disk_top_directory = g_strdup (item_on_disk);
1268
1269         /* Walk up in the hierarchy, finding the top-most point that still
1270          * matches our device ID -- the root directory of the volume.
1271          */
1272         for (;;) {
1273                 char *previous_search_directory;
1274                 char *last_slash;
1275                 
1276                 previous_search_directory = g_strdup (disk_top_directory);
1277                 last_slash = strrchr (disk_top_directory, '/');
1278                 if (last_slash == NULL) {
1279                         g_free (previous_search_directory);
1280                         break;
1281                 }
1282                 
1283                 *last_slash = '\0';
1284                 if (lstat (disk_top_directory, &stat_buffer) < 0
1285                         || stat_buffer.st_dev != near_device_id) {
1286                         /* we ran past the root of the disk we are exploring */
1287                         g_free (disk_top_directory);
1288                         disk_top_directory = previous_search_directory;
1289                         break;
1290                 }
1291                 /* FIXME bugzilla.eazel.com 2733: This must result in
1292                  * a cancelled error, but there's no way for the
1293                  * caller to know that. We probably have to add a
1294                  * GnomeVFSResult to this function.  
1295                  */
1296                 if (gnome_vfs_context_check_cancellation (context)) {
1297                         g_free (previous_search_directory);
1298                         g_free (disk_top_directory);
1299                         return NULL;
1300                 }
1301         }
1302         return disk_top_directory;
1303 }
1304
1305 #define TRASH_ENTRY_CACHE_PARENT ".gnome/gnome-vfs"
1306 #define TRASH_ENTRY_CACHE_NAME ".trash_entry_cache"
1307 #define NON_EXISTENT_TRASH_ENTRY "-"
1308
1309 /* Save the localy cached Trashed paths on disk in the user's home
1310  * directory.
1311  */
1312 static void
1313 save_trash_entry_cache (void)
1314 {
1315         int cache_file;
1316         char *cache_file_parent, *cache_file_path;
1317         GList *p;
1318         char *buffer, *escaped_path, *escaped_mount_point;
1319
1320         cache_file_parent = append_to_path (g_get_home_dir (), TRASH_ENTRY_CACHE_PARENT);
1321         cache_file_path = append_to_path (cache_file_parent, TRASH_ENTRY_CACHE_NAME);
1322
1323         if (mkdir_recursive (cache_file_parent, 0777) != 0) {
1324                 g_warning ("failed to create trash item cache file");
1325                 return;
1326         }
1327
1328         cache_file = open (cache_file_path, O_CREAT | O_TRUNC | O_RDWR, 0666);
1329         if (cache_file < 0) {
1330                 g_warning ("failed to create trash item cache file");
1331                 return;
1332         }
1333
1334         for (p = cached_trash_directories; p != NULL; p = p->next) {
1335                 /* Use proper escaping to not confuse paths with spaces in them */
1336                 escaped_path = gnome_vfs_escape_path_string (
1337                         ((TrashDirectoryCachedItem *)p->data)->path);
1338                 escaped_mount_point = gnome_vfs_escape_path_string(
1339                         ((TrashDirectoryCachedItem *)p->data)->device_mount_point);
1340                         
1341                 buffer = g_strdup_printf ("%s %s\n", escaped_mount_point, escaped_path);
1342                 write (cache_file, buffer, strlen (buffer));
1343
1344 #ifdef DEBUG_FIND_DIRECTORY
1345         g_print ("saving trash item cache %s\n", buffer);
1346 #endif
1347
1348                 g_free (buffer);
1349                 g_free (escaped_mount_point);
1350                 g_free (escaped_path);
1351         }
1352         close (cache_file);
1353         
1354         g_free (cache_file_path);
1355         g_free (cache_file_parent);
1356 }
1357
1358 typedef struct {
1359         const char *mount_point;
1360         const char *trash_path;
1361         dev_t device_id;
1362         gboolean done;
1363 } UpdateOneCachedEntryContext;
1364
1365 /* Updates one entry in the local Trash item cache to reflect the
1366  * location we just found or in which we created a new Trash.
1367  */
1368 static void
1369 update_one_cached_trash_entry (gpointer element, gpointer cast_to_context)
1370 {
1371         UpdateOneCachedEntryContext *context;
1372         TrashDirectoryCachedItem *item;
1373
1374         context = (UpdateOneCachedEntryContext *)cast_to_context;
1375         item = (TrashDirectoryCachedItem *)element;
1376
1377         if (context->done) {
1378                 /* We already took care of business in a previous iteration. */
1379                 return;
1380         }
1381
1382         if (strcmp (context->mount_point, item->device_mount_point) == 0) {
1383                 /* This is the item we are looking for, update it. */
1384                 g_free (item->path);
1385                 item->path = g_strdup (context->trash_path);
1386                 item->device_id = context->device_id;
1387
1388                 /* no more work */
1389                 context->done = TRUE;
1390         }
1391 }
1392
1393 static void
1394 add_local_cached_trash_entry (dev_t near_device_id, const char *trash_path, const char *mount_point)
1395 {
1396         TrashDirectoryCachedItem *new_cached_item;
1397         UpdateOneCachedEntryContext update_context;
1398
1399         /* First check if we already have an entry for this mountpoint,
1400          * if so, update it.
1401          */
1402
1403         update_context.mount_point = mount_point;
1404         update_context.trash_path = trash_path;
1405         update_context.device_id = near_device_id;
1406         update_context.done = FALSE;
1407
1408         g_list_foreach (cached_trash_directories, update_one_cached_trash_entry, &update_context);
1409         if (update_context.done) {
1410                 /* Sucessfully updated, no more work left. */
1411                 return;
1412         }
1413         
1414         /* Save the new trash item to the local cache. */
1415         new_cached_item = g_new (TrashDirectoryCachedItem, 1);
1416         new_cached_item->path = g_strdup (trash_path);
1417         new_cached_item->device_mount_point = g_strdup (mount_point);
1418         new_cached_item->device_id = near_device_id;
1419
1420
1421         cached_trash_directories = g_list_prepend (cached_trash_directories, new_cached_item);
1422 }
1423
1424 static void
1425 add_cached_trash_entry (dev_t near_device_id, const char *trash_path, const char *mount_point)
1426 {
1427         add_local_cached_trash_entry (near_device_id, trash_path, mount_point);
1428         /* write out the local cache */
1429         save_trash_entry_cache ();
1430 }
1431
1432 static void
1433 destroy_cached_trash_entry (TrashDirectoryCachedItem *entry)
1434 {
1435         g_free (entry->path);
1436         g_free (entry->device_mount_point);
1437         g_free (entry);
1438 }
1439
1440 /* Read the cached entries for the file cache into the local Trash item cache. */
1441 static void
1442 read_saved_cached_trash_entries (void)
1443 {
1444         char *cache_file_path;
1445         FILE *cache_file;
1446         char buffer[2048];
1447         char escaped_mount_point[PATH_MAX], escaped_trash_path[PATH_MAX];
1448         char *mount_point, *trash_path;
1449         struct stat stat_buffer;
1450
1451         /* empty the old locally cached entries */
1452         g_list_foreach (cached_trash_directories, 
1453                 (GFunc)destroy_cached_trash_entry, NULL);
1454         g_list_free (cached_trash_directories);
1455         cached_trash_directories = NULL;
1456
1457         /* read in the entries from disk */
1458         cache_file_path = g_strconcat (g_get_home_dir (), G_DIR_SEPARATOR_S,
1459                 TRASH_ENTRY_CACHE_PARENT, G_DIR_SEPARATOR_S, TRASH_ENTRY_CACHE_NAME, NULL);
1460         cache_file = fopen (cache_file_path, "r");
1461
1462         if (cache_file != NULL) {
1463                 for (;;) {
1464                         if (fgets (buffer, sizeof (buffer), cache_file) == NULL) {
1465                                 break;
1466                         }
1467
1468                         mount_point = NULL;
1469                         trash_path = NULL;
1470                         if (sscanf (buffer, "%s %s", escaped_mount_point, escaped_trash_path) == 2) {
1471                                 /* the paths are saved in escaped form */
1472                                 trash_path = gnome_vfs_unescape_string (escaped_trash_path, "/");
1473                                 mount_point = gnome_vfs_unescape_string (escaped_mount_point, "/"); 
1474
1475                                 if (trash_path != NULL 
1476                                         && mount_point != NULL
1477                                         && (strcmp (trash_path, NON_EXISTENT_TRASH_ENTRY) == 0 || lstat (trash_path, &stat_buffer) == 0)
1478                                         && lstat (mount_point, &stat_buffer) == 0) {
1479                                         /* We either know the trash doesn't exist or we checked that it's really
1480                                          * there - this is a good entry, copy it into the local cache.
1481                                          */
1482                                          add_local_cached_trash_entry (stat_buffer.st_dev, trash_path, mount_point);
1483 #ifdef DEBUG_FIND_DIRECTORY
1484                                         g_print ("read trash item cache entry %s %s\n", trash_path, mount_point);
1485 #endif
1486                                 }
1487                         }
1488                         
1489                         g_free (trash_path);
1490                         g_free (mount_point);
1491                 }
1492                 fclose (cache_file);    
1493         }
1494         
1495         g_free (cache_file_path);
1496 }
1497
1498 /* Create a Trash directory on the same disk as @full_name_near. */
1499 static char *
1500 create_trash_near (const char *full_name_near, dev_t near_device_id, const char *disk_top_directory,
1501         guint permissions, GnomeVFSContext *context)
1502 {
1503         return try_creating_trash_in (disk_top_directory, permissions);
1504 }
1505
1506
1507 static gboolean
1508 cached_trash_entry_exists (const TrashDirectoryCachedItem *entry)
1509 {
1510         struct stat stat_buffer;
1511         return lstat (entry->path, &stat_buffer) == 0;
1512 }
1513
1514 /* Search through the local cache looking for an entry that matches a given
1515  * device ID. If @check_disk specified, check if the entry we found actually exists.
1516  */
1517 static char *
1518 find_locally_cached_trash_entry_for_device_id (dev_t device_id, gboolean check_disk)
1519 {
1520         GList *matching_item;
1521         FindByDeviceIDParameters tmp;
1522         const char *trash_path;
1523
1524         tmp.device_id = device_id;
1525
1526         matching_item = g_list_find_custom (cached_trash_directories, 
1527                 &tmp, match_trash_item_by_device_id);
1528
1529         if (matching_item == NULL) {
1530                 return NULL;
1531         }
1532
1533         trash_path = ((TrashDirectoryCachedItem *)matching_item->data)->path;
1534
1535         if (trash_path == NULL) {
1536                 /* we already know that this disk does not contain a trash directory */
1537 #ifdef DEBUG_FIND_DIRECTORY
1538                 g_print ("cache indicates no trash for %s \n", trash_path);
1539 #endif
1540                 return g_strdup (NON_EXISTENT_TRASH_ENTRY);
1541         }
1542
1543         if (check_disk) {
1544                 /* We found something, make sure it still exists. */
1545                 if (strcmp (((TrashDirectoryCachedItem *)matching_item->data)->path, NON_EXISTENT_TRASH_ENTRY) != 0
1546                         && !cached_trash_entry_exists ((TrashDirectoryCachedItem *)matching_item->data)) {
1547                         /* The cached item doesn't really exist, make a new one
1548                          * and delete the cached entry
1549                          */
1550 #ifdef DEBUG_FIND_DIRECTORY
1551                         g_print ("entry %s doesn't exist, removing \n", 
1552                                 ((TrashDirectoryCachedItem *)matching_item->data)->path);
1553 #endif
1554                         destroy_cached_trash_entry ((TrashDirectoryCachedItem *)matching_item->data);
1555                         cached_trash_directories = g_list_remove (cached_trash_directories, 
1556                                 matching_item->data);
1557                         return NULL;
1558                 }
1559         }
1560
1561 #ifdef DEBUG_FIND_DIRECTORY
1562         g_print ("local cache found %s \n", trash_path);
1563 #endif
1564         g_assert (matching_item != NULL);
1565         return g_strdup (trash_path);
1566 }
1567
1568 /* Look for an entry in the file and local caches. */
1569 static char *
1570 find_cached_trash_entry_for_device (dev_t device_id, gboolean check_disk)
1571 {
1572         if (cached_trash_directories == NULL) {
1573                 if (!check_disk) {
1574                         return NULL;
1575                 }
1576                 read_saved_cached_trash_entries ();
1577         }
1578         return find_locally_cached_trash_entry_for_device_id (device_id, check_disk);
1579 }
1580
1581 /* Search for a Trash entry or create one. Called when there is no cached entry. */
1582 static char *
1583 find_or_create_trash_near (const char *full_name_near, dev_t near_device_id, 
1584         gboolean create_if_needed, gboolean find_if_needed, guint permissions, 
1585         GnomeVFSContext *context)
1586 {
1587         char *result;
1588         char *disk_top_directory;
1589
1590         result = NULL;
1591         /* figure out the topmost disk directory */
1592         disk_top_directory = find_disk_top_directory (full_name_near, 
1593                 near_device_id, context);
1594
1595         if (disk_top_directory == NULL) {
1596                 /* Failed to find it, don't look at this disk until we
1597                  * are ready to try to create a Trash on it again.
1598                  */
1599 #ifdef DEBUG_FIND_DIRECTORY
1600                 g_print ("failed to find top disk directory for %s\n", full_name_near);
1601 #endif
1602                 add_cached_trash_entry (near_device_id, NON_EXISTENT_TRASH_ENTRY, disk_top_directory);
1603                 return NULL;
1604         }
1605
1606         if (find_if_needed) {
1607                 /* figure out the topmost disk directory */
1608                 result = find_trash_in_hierarchy (disk_top_directory, near_device_id, context);
1609                 if (result == NULL) {
1610                         /* We just found out there is no Trash on the disk, 
1611                          * remember this for next time.
1612                          */
1613                         result = g_strdup(NON_EXISTENT_TRASH_ENTRY);
1614                 }
1615         }
1616
1617         if (result == NULL && create_if_needed) {
1618                 /* didn't find a Trash, create one */
1619                 result = create_trash_near (full_name_near, near_device_id, disk_top_directory,
1620                         permissions, context);
1621         }
1622
1623         if (result != NULL) {
1624                 /* remember whatever we found for next time */
1625                 add_cached_trash_entry (near_device_id, result, disk_top_directory);
1626         }
1627
1628         g_free (disk_top_directory);
1629
1630         return result;
1631 }
1632
1633 /* Find or create a trash directory on the same disk as @full_name_near. Check
1634  * the local and file cache for matching Trash entries first.
1635  *
1636  *     This is the only entry point for the trash cache code,
1637  * we holds the lock while operating on it only here.
1638  */
1639 static char *
1640 find_trash_directory (const char *full_name_near, dev_t near_device_id, 
1641                       gboolean create_if_needed, gboolean find_if_needed,
1642                       guint permissions, GnomeVFSContext *context)
1643 {
1644         char *result;
1645
1646         G_LOCK (cached_trash_directories);
1647
1648         /* look in the saved trash locations first */
1649         result = find_cached_trash_entry_for_device (near_device_id, find_if_needed);
1650
1651         if (find_if_needed) {
1652                 if (result != NULL && strcmp (result, NON_EXISTENT_TRASH_ENTRY) == 0 && create_if_needed) {
1653                         /* We know there is no Trash yet because we remember
1654                          * from the last time we looked.
1655                          * If we were asked to create one, ignore the fact that
1656                          * we already looked for it, look again and create a
1657                          * new trash if we find nothing. 
1658                          */
1659 #ifdef DEBUG_FIND_DIRECTORY
1660                         g_print ("cache indicates no trash for %s, force a creation \n", full_name_near);
1661 #endif
1662                         g_free (result);
1663                         result = NULL;
1664                 }
1665
1666                 if (result == NULL) {
1667                         /* No luck sofar. Look for the Trash on the disk, optionally create it
1668                          * if we find nothing.
1669                          */
1670                         result = find_or_create_trash_near (full_name_near, near_device_id, 
1671                                 create_if_needed, find_if_needed, permissions, context);
1672                 }
1673         } else if (create_if_needed) {
1674                 if (result == NULL || strcmp (result, NON_EXISTENT_TRASH_ENTRY) == 0) {
1675                         result = find_or_create_trash_near (full_name_near, near_device_id, 
1676                                 create_if_needed, find_if_needed, permissions, context);
1677                 }
1678         }
1679         
1680         if (result != NULL && strcmp(result, NON_EXISTENT_TRASH_ENTRY) == 0) {
1681                 /* This means that we know there is no Trash */
1682                 g_free (result);
1683                 result = NULL;
1684         }
1685
1686         G_UNLOCK (cached_trash_directories);
1687         
1688         return result;
1689 }
1690
1691 static GnomeVFSResult
1692 do_find_directory (GnomeVFSMethod *method,
1693                    GnomeVFSURI *near_uri,
1694                    GnomeVFSFindDirectoryKind kind,
1695                    GnomeVFSURI **result_uri,
1696                    gboolean create_if_needed,
1697                    gboolean find_if_needed,
1698                    guint permissions,
1699                    GnomeVFSContext *context)
1700 {
1701         gint retval;
1702         char *full_name_near;
1703         struct stat near_item_stat;
1704         struct stat home_volume_stat;
1705         const char *home_directory;
1706         char *target_directory_path;
1707         char *target_directory_uri;
1708
1709         
1710         target_directory_path = NULL;
1711         *result_uri = NULL;
1712
1713         full_name_near = get_path_from_uri (near_uri);
1714         if (full_name_near == NULL)
1715                 return GNOME_VFS_ERROR_INVALID_URI;
1716
1717         /* We will need the URI and the stat structure for the home directory. */
1718         home_directory = g_get_home_dir ();
1719
1720         if (gnome_vfs_context_check_cancellation (context)) {
1721                 g_free (full_name_near);
1722                 return GNOME_VFS_ERROR_CANCELLED;
1723         }
1724
1725         retval = lstat (full_name_near, &near_item_stat);
1726         if (retval != 0) {
1727                 g_free (full_name_near);
1728                 return gnome_vfs_result_from_errno ();
1729         }
1730
1731         if (gnome_vfs_context_check_cancellation (context)) {
1732                 g_free (full_name_near);
1733                 return GNOME_VFS_ERROR_CANCELLED;
1734         }
1735         
1736         retval = stat (home_directory, &home_volume_stat);
1737         if (retval != 0) {
1738                 g_free (full_name_near);
1739                 return gnome_vfs_result_from_errno ();
1740         }
1741         
1742         if (gnome_vfs_context_check_cancellation (context)) {
1743                 g_free (full_name_near);
1744                 return GNOME_VFS_ERROR_CANCELLED;
1745         }
1746
1747         switch (kind) {
1748         case GNOME_VFS_DIRECTORY_KIND_TRASH:
1749                 /* Use 0700 (S_IRWXU) for the permissions,
1750                  * regardless of the requested permissions, so other
1751                  * users can't view the trash files.
1752                  */
1753                 permissions = S_IRWXU;  
1754                 if (near_item_stat.st_dev != home_volume_stat.st_dev) {
1755                         /* This volume does not contain our home, we have to find/create the Trash
1756                          * elsewhere on the volume. Use a heuristic to find a good place.
1757                          */
1758                         FindByDeviceIDParameters tmp;
1759                         tmp.device_id = near_item_stat.st_dev;
1760
1761                         if (gnome_vfs_context_check_cancellation (context))
1762                                 return GNOME_VFS_ERROR_CANCELLED;
1763
1764                         target_directory_path = find_trash_directory (full_name_near,  
1765                                 near_item_stat.st_dev, create_if_needed, find_if_needed,
1766                                 permissions, context);
1767
1768                         if (gnome_vfs_context_check_cancellation (context)) {
1769                                 return GNOME_VFS_ERROR_CANCELLED;
1770                         }
1771                 } else  {
1772                         /* volume with a home directory, just create a trash in home */
1773                         target_directory_path = append_to_path (home_directory, TRASH_DIRECTORY_NAME_BASE);
1774                 }
1775                 break;
1776                 
1777         case GNOME_VFS_DIRECTORY_KIND_DESKTOP:
1778                 if (near_item_stat.st_dev != home_volume_stat.st_dev) {
1779                         /* unsupported */
1780                         break;
1781                 }
1782                 target_directory_path = append_to_path (home_directory, "Desktop");
1783                 break;
1784
1785         default:
1786                 break;
1787         }
1788
1789         g_free (full_name_near);
1790
1791         if (target_directory_path == NULL) {
1792                 return GNOME_VFS_ERROR_NOT_SUPPORTED;
1793         }
1794
1795         if (create_if_needed && access (target_directory_path, F_OK) != 0) {
1796                 mkdir_recursive (target_directory_path, permissions);
1797         }
1798
1799         if (access (target_directory_path, F_OK) != 0) {
1800                 g_free (target_directory_path);
1801                 return GNOME_VFS_ERROR_NOT_FOUND;
1802         }
1803
1804         target_directory_uri = gnome_vfs_get_uri_from_local_path (target_directory_path);
1805         g_free (target_directory_path);
1806         *result_uri = gnome_vfs_uri_new (target_directory_uri);
1807         g_free (target_directory_uri);
1808
1809         return GNOME_VFS_OK;
1810 }
1811
1812 static GnomeVFSResult
1813 rename_helper (const gchar *old_full_name,
1814                const gchar *new_full_name,
1815                gboolean force_replace,
1816                GnomeVFSContext *context)
1817 {
1818         gboolean old_exists;
1819         struct stat statbuf;
1820         gint retval;
1821
1822         retval = stat (new_full_name, &statbuf);
1823         if (retval == 0) {
1824                 /* If we are not allowed to replace an existing file, return an
1825                    error.  */
1826                 if (! force_replace)
1827                         return GNOME_VFS_ERROR_FILE_EXISTS;
1828                 old_exists = TRUE;
1829         } else {
1830                 old_exists = FALSE;
1831         }
1832
1833         if (gnome_vfs_context_check_cancellation (context))
1834                 return GNOME_VFS_ERROR_CANCELLED;
1835
1836         retval = rename (old_full_name, new_full_name);
1837
1838         /* FIXME bugzilla.eazel.com 1186: The following assumes that,
1839          * if `new_uri' and `old_uri' are on different file systems,
1840          * `rename()' will always return `EXDEV' instead of `EISDIR',
1841          * even if the old file is not a directory while the new one
1842          * is. If this is not the case, we have to stat() both the
1843          * old and new file.
1844          */
1845         if (retval != 0 && errno == EISDIR && force_replace && old_exists) {
1846                 /* The Unix version of `rename()' fails if the original file is
1847                    not a directory, while the new one is.  But we have been
1848                    explicitly asked to replace the destination name, so if the
1849                    new name points to a directory, we remove it manually.  */
1850                 if (S_ISDIR (statbuf.st_mode)) {
1851                         if (gnome_vfs_context_check_cancellation (context))
1852                                 return GNOME_VFS_ERROR_CANCELLED;
1853                         retval = rmdir (new_full_name);
1854                         if (retval != 0) {
1855                                 return gnome_vfs_result_from_errno ();
1856                         }
1857
1858                         if (gnome_vfs_context_check_cancellation (context))
1859                                 return GNOME_VFS_ERROR_CANCELLED;
1860
1861                         retval = rename (old_full_name, new_full_name);
1862                 }
1863         }
1864
1865         if (retval != 0) {
1866                 return gnome_vfs_result_from_errno ();
1867         }
1868
1869         return GNOME_VFS_OK;
1870 }
1871
1872 static GnomeVFSResult
1873 do_move (GnomeVFSMethod *method,
1874          GnomeVFSURI *old_uri,
1875          GnomeVFSURI *new_uri,
1876          gboolean force_replace,
1877          GnomeVFSContext *context)
1878 {
1879         gchar *old_full_name;
1880         gchar *new_full_name;
1881         GnomeVFSResult result;
1882
1883         old_full_name = get_path_from_uri (old_uri);
1884         if (old_full_name == NULL)
1885                 return GNOME_VFS_ERROR_INVALID_URI;
1886
1887         new_full_name = get_path_from_uri (new_uri);
1888         if (new_full_name == NULL) {
1889                 g_free (old_full_name);
1890                 return GNOME_VFS_ERROR_INVALID_URI;
1891         }
1892
1893         result = rename_helper (old_full_name, new_full_name,
1894                                 force_replace, context);
1895
1896         g_free (old_full_name);
1897         g_free (new_full_name);
1898
1899         return result;
1900 }
1901
1902 static GnomeVFSResult
1903 do_unlink (GnomeVFSMethod *method,
1904            GnomeVFSURI *uri,
1905            GnomeVFSContext *context)
1906 {
1907         gchar *full_name;
1908         gint retval;
1909
1910         full_name = get_path_from_uri (uri);
1911         if (full_name == NULL) {
1912                 return GNOME_VFS_ERROR_INVALID_URI;
1913         }
1914
1915         retval = unlink (full_name);
1916
1917         g_free (full_name);
1918
1919         if (retval != 0) {
1920                 return gnome_vfs_result_from_errno ();
1921         }
1922
1923         return GNOME_VFS_OK;
1924 }
1925
1926 static GnomeVFSResult
1927 do_create_symbolic_link (GnomeVFSMethod *method,
1928                          GnomeVFSURI *uri,
1929                          const char *target_reference,
1930                          GnomeVFSContext *context)
1931 {
1932         const char *link_scheme, *target_scheme;
1933         char *link_full_name, *target_full_name;
1934         GnomeVFSResult result;
1935         GnomeVFSURI *target_uri;
1936
1937         g_assert (target_reference != NULL);
1938         g_assert (uri != NULL);
1939         
1940         /* what we actually want is a function that takes a const char * and 
1941          * tells whether it is a valid URI
1942          */
1943         target_uri = gnome_vfs_uri_new (target_reference);
1944         if (target_uri == NULL) {
1945                 return GNOME_VFS_ERROR_INVALID_URI;
1946         }
1947
1948         link_scheme = gnome_vfs_uri_get_scheme (uri);
1949         g_assert (link_scheme != NULL);
1950
1951         target_scheme = gnome_vfs_uri_get_scheme (target_uri);
1952         if (target_scheme == NULL) {
1953                 target_scheme = "file";
1954         }
1955         
1956         if ((strcmp (link_scheme, "file") == 0) && (strcmp (target_scheme, "file") == 0)) {
1957                 /* symlink between two places on the local filesystem */
1958                 if (strncmp (target_reference, "file", 4) != 0) {
1959                         /* target_reference wasn't a full URI */
1960                         target_full_name = strdup (target_reference); 
1961                 } else {
1962                         target_full_name = get_path_from_uri (target_uri);
1963                 }
1964
1965                 link_full_name = get_path_from_uri (uri);
1966
1967                 if (symlink (target_full_name, link_full_name) != 0) {
1968                         result = gnome_vfs_result_from_errno ();
1969                 } else {
1970                         result = GNOME_VFS_OK;
1971                 }
1972
1973                 g_free (target_full_name);
1974                 g_free (link_full_name);
1975         } else {
1976                 /* FIXME bugzilla.eazel.com 2792: do a URI link */
1977                 result = GNOME_VFS_ERROR_NOT_SUPPORTED;
1978         }
1979
1980         gnome_vfs_uri_unref (target_uri);
1981
1982         return result;
1983 }
1984
1985 /* When checking whether two locations are on the same file system, we are
1986    doing this to determine whether we can recursively move or do other
1987    sorts of transfers.  When a symbolic link is the "source", its
1988    location is the location of the link file, because we want to
1989    know about transferring the link, whereas for symbolic links that
1990    are "targets", we use the location of the object being pointed to,
1991    because that is where we will be moving/copying to. */
1992 static GnomeVFSResult
1993 do_check_same_fs (GnomeVFSMethod *method,
1994                   GnomeVFSURI *source_uri,
1995                   GnomeVFSURI *target_uri,
1996                   gboolean *same_fs_return,
1997                   GnomeVFSContext *context)
1998 {
1999         gchar *full_name_source, *full_name_target;
2000         struct stat s_source, s_target;
2001         gint retval;
2002
2003         full_name_source = get_path_from_uri (source_uri);
2004         retval = lstat (full_name_source, &s_source);
2005         g_free (full_name_source);
2006
2007         if (retval != 0)
2008                 return gnome_vfs_result_from_errno ();
2009
2010         if (gnome_vfs_context_check_cancellation (context))
2011                 return GNOME_VFS_ERROR_CANCELLED;
2012  
2013         full_name_target = get_path_from_uri (target_uri);
2014         retval = stat (full_name_target, &s_target);
2015         g_free (full_name_target);
2016
2017         if (retval != 0)
2018                 return gnome_vfs_result_from_errno ();
2019
2020         *same_fs_return = (s_source.st_dev == s_target.st_dev);
2021
2022         return GNOME_VFS_OK;
2023 }
2024
2025 static GnomeVFSResult
2026 do_set_file_info (GnomeVFSMethod *method,
2027                   GnomeVFSURI *uri,
2028                   const GnomeVFSFileInfo *info,
2029                   GnomeVFSSetFileInfoMask mask,
2030                   GnomeVFSContext *context)
2031 {
2032         gchar *full_name;
2033
2034         full_name = get_path_from_uri (uri);
2035         if (full_name == NULL)
2036                 return GNOME_VFS_ERROR_INVALID_URI;
2037
2038         if (mask & GNOME_VFS_SET_FILE_INFO_NAME) {
2039                 GnomeVFSResult result;
2040                 gchar *dir, *encoded_dir;
2041                 gchar *new_name;
2042
2043                 encoded_dir = gnome_vfs_uri_extract_dirname (uri);
2044                 dir = gnome_vfs_unescape_string (encoded_dir, G_DIR_SEPARATOR_S);
2045                 g_free (encoded_dir);
2046                 g_assert (dir != NULL);
2047
2048                 /* FIXME bugzilla.eazel.com 645: This needs to return
2049                  * an error for incoming names with "/" characters in
2050                  * them, instead of moving the file.
2051                  */
2052
2053                 if (dir[strlen(dir) - 1] != '/') {
2054                         new_name = g_strconcat (dir, "/", info->name, NULL);
2055                 } else {
2056                         new_name = g_strconcat (dir, info->name, NULL);
2057                 }
2058
2059                 result = rename_helper (full_name, new_name, FALSE, context);
2060
2061                 g_free (dir);
2062                 g_free (new_name);
2063
2064                 if (result != GNOME_VFS_OK) {
2065                         g_free (full_name);
2066                         return result;
2067                 }
2068         }
2069
2070         if (gnome_vfs_context_check_cancellation (context)) {
2071                 g_free (full_name);
2072                 return GNOME_VFS_ERROR_CANCELLED;
2073         }
2074
2075         if (mask & GNOME_VFS_SET_FILE_INFO_PERMISSIONS) {
2076                 if (chmod (full_name, info->permissions) != 0) {
2077                         g_free (full_name);
2078                         return gnome_vfs_result_from_errno ();
2079                 }
2080         }
2081
2082         if (gnome_vfs_context_check_cancellation (context)) {
2083                 g_free (full_name);
2084                 return GNOME_VFS_ERROR_CANCELLED;
2085         }
2086
2087         if (mask & GNOME_VFS_SET_FILE_INFO_OWNER) {
2088                 if (chown (full_name, info->uid, info->gid) != 0) {
2089                         g_free (full_name);
2090                         return gnome_vfs_result_from_errno ();
2091                 }
2092         }
2093
2094         if (gnome_vfs_context_check_cancellation (context)) {
2095                 g_free (full_name);
2096                 return GNOME_VFS_ERROR_CANCELLED;
2097         }
2098
2099         if (mask & GNOME_VFS_SET_FILE_INFO_TIME) {
2100                 struct utimbuf utimbuf;
2101
2102                 utimbuf.actime = info->atime;
2103                 utimbuf.modtime = info->mtime;
2104
2105                 if (utime (full_name, &utimbuf) != 0) {
2106                         g_free (full_name);
2107                         return gnome_vfs_result_from_errno ();
2108                 }
2109         }
2110
2111         g_free (full_name);
2112
2113         return GNOME_VFS_OK;
2114 }
2115
2116 #ifdef HAVE_FAM
2117 static gboolean
2118 fam_do_iter_unlocked (void)
2119 {
2120         while (fam_connection != NULL && FAMPending(fam_connection)) {
2121                 FAMEvent ev;
2122                 FileMonitorHandle *handle;
2123                 gboolean cancelled;
2124                 GnomeVFSMonitorEventType event_type;
2125
2126                 if (FAMNextEvent(fam_connection, &ev) != 1) {
2127                         FAMClose(fam_connection);
2128                         g_free(fam_connection);
2129                         fam_connection = NULL;
2130                         return FALSE;
2131                 }
2132
2133                 handle = (FileMonitorHandle *)ev.userdata;
2134                 cancelled = handle->cancelled;
2135                 event_type = -1;
2136
2137                 switch (ev.code) {
2138                         case FAMChanged:
2139                                 event_type = GNOME_VFS_MONITOR_EVENT_CHANGED;
2140                                 break;
2141                         case FAMDeleted:
2142                                 event_type = GNOME_VFS_MONITOR_EVENT_DELETED;
2143                                 break;
2144                         case FAMStartExecuting:
2145                                 event_type = GNOME_VFS_MONITOR_EVENT_STARTEXECUTING;
2146                                 break;
2147                         case FAMStopExecuting:
2148                                 event_type = GNOME_VFS_MONITOR_EVENT_STOPEXECUTING;
2149                                 break;
2150                         case FAMCreated:
2151                                 event_type = GNOME_VFS_MONITOR_EVENT_CREATED;
2152                                 break;
2153                         case FAMAcknowledge:
2154                                 if (handle->cancelled) {
2155                                         gnome_vfs_uri_unref (handle->uri);
2156                                         g_free (handle);
2157                                 }
2158                                 break;
2159                         case FAMExists:
2160                         case FAMEndExist:
2161                         case FAMMoved:
2162                                 /* Not supported */
2163                                 break;
2164                 }
2165
2166                 if (event_type != -1 && !cancelled) {
2167                         GnomeVFSURI *info_uri;
2168                         gchar *info_str;
2169
2170                         /* 
2171                          * FAM can send events with either a absolute or
2172                          * relative (from the monitored URI) path, so check if
2173                          * the filename starts with '/'.  
2174                          */
2175                         if (ev.filename[0] == '/') {
2176                                 info_str = gnome_vfs_get_uri_from_local_path (ev.filename);
2177                                 info_uri = gnome_vfs_uri_new (info_str);
2178                                 g_free (info_str);
2179                         } else
2180                                 info_uri = gnome_vfs_uri_append_file_name (handle->uri, ev.filename);
2181
2182                         /* This queues an idle, so there are no reentrancy issues */
2183                         gnome_vfs_monitor_callback ((GnomeVFSMethodHandle *)handle,
2184                                                     info_uri, 
2185                                                     event_type);
2186                         gnome_vfs_uri_unref (info_uri);
2187                 }
2188         }
2189
2190         return TRUE;
2191 }
2192
2193 static gboolean
2194 fam_callback (GIOChannel *source,
2195               GIOCondition condition,
2196               gpointer data)
2197 {
2198         gboolean res;
2199         G_LOCK (fam_connection);
2200
2201         res = fam_do_iter_unlocked ();
2202
2203         G_UNLOCK (fam_connection);
2204
2205         return res;
2206 }
2207
2208
2209
2210 static gboolean
2211 monitor_setup (void)
2212 {
2213         GIOChannel *ioc;
2214         gint watch_id;
2215
2216         G_LOCK (fam_connection);
2217
2218         if (fam_connection == NULL) {
2219                 fam_connection = g_malloc0(sizeof(FAMConnection));
2220                 if (FAMOpen2(fam_connection, "test-monitor") != 0) {
2221 #ifdef DEBUG_FAM
2222                         g_print ("FAMOpen failed, FAMErrno=%d\n", FAMErrno);
2223 #endif
2224                         g_free(fam_connection);
2225                         fam_connection = NULL;
2226                         G_UNLOCK (fam_connection);
2227                         return FALSE;
2228                 }
2229                 ioc = g_io_channel_unix_new (FAMCONNECTION_GETFD(fam_connection));
2230                 watch_id = g_io_add_watch (ioc,
2231                                            G_IO_IN | G_IO_HUP | G_IO_ERR,
2232                                            fam_callback, fam_connection);
2233                 g_io_channel_unref (ioc);
2234         }
2235
2236         G_UNLOCK (fam_connection);
2237
2238         return TRUE;
2239 }
2240 #endif
2241
2242 static GnomeVFSResult
2243 do_monitor_add (GnomeVFSMethod *method,
2244                 GnomeVFSMethodHandle **method_handle_return,
2245                 GnomeVFSURI *uri,
2246                 GnomeVFSMonitorType monitor_type)
2247 {
2248 #ifdef HAVE_FAM
2249         FileMonitorHandle *handle;
2250         char *filename;
2251
2252         if (!monitor_setup ()) {
2253                 return GNOME_VFS_ERROR_NOT_SUPPORTED;
2254         }
2255
2256         handle = g_new0 (FileMonitorHandle, 1);
2257         handle->uri = uri;
2258         handle->cancelled = FALSE;
2259         gnome_vfs_uri_ref (uri);
2260         filename = get_path_from_uri (uri);
2261
2262         G_LOCK (fam_connection);
2263         /* We need to queue up incoming messages to avoid blocking on write
2264            if there are many monitors being added */
2265         fam_do_iter_unlocked ();
2266
2267         if (fam_connection == NULL) {
2268                 G_UNLOCK (fam_connection);
2269                 return GNOME_VFS_ERROR_NOT_SUPPORTED;
2270         }
2271         
2272         if (monitor_type == GNOME_VFS_MONITOR_FILE) {
2273                 FAMMonitorFile (fam_connection, filename, 
2274                         &handle->request, handle);
2275         } else {
2276                 FAMMonitorDirectory (fam_connection, filename, 
2277                         &handle->request, handle);
2278         }
2279
2280         G_UNLOCK (fam_connection);
2281         
2282         *method_handle_return = (GnomeVFSMethodHandle *)handle;
2283
2284         g_free (filename);
2285
2286         return GNOME_VFS_OK;
2287 #else
2288         return GNOME_VFS_ERROR_NOT_SUPPORTED;
2289 #endif
2290 }
2291
2292 static GnomeVFSResult
2293 do_monitor_cancel (GnomeVFSMethod *method,
2294                    GnomeVFSMethodHandle *method_handle)
2295 {
2296 #ifdef HAVE_FAM
2297         FileMonitorHandle *handle = (FileMonitorHandle *)method_handle;
2298
2299         if (!monitor_setup ()) {
2300                 return GNOME_VFS_ERROR_NOT_SUPPORTED;
2301         }
2302
2303         if (handle->cancelled)
2304                 return GNOME_VFS_OK;
2305
2306         handle->cancelled = TRUE;
2307         G_LOCK (fam_connection);
2308
2309         /* We need to queue up incoming messages to avoid blocking on write
2310            if there are many monitors being canceled */
2311         fam_do_iter_unlocked ();
2312
2313         if (fam_connection == NULL) {
2314                 G_UNLOCK (fam_connection);
2315                 return GNOME_VFS_ERROR_NOT_SUPPORTED;
2316         }
2317         
2318         FAMCancelMonitor (fam_connection, &handle->request);
2319         G_UNLOCK (fam_connection);
2320
2321         return GNOME_VFS_OK;
2322 #else
2323         return GNOME_VFS_ERROR_NOT_SUPPORTED;
2324 #endif
2325 }
2326
2327 static GnomeVFSResult
2328 do_file_control (GnomeVFSMethod *method,
2329                  GnomeVFSMethodHandle *method_handle,
2330                  const char *operation,
2331                  gpointer operation_data,
2332                  GnomeVFSContext *context)
2333 {
2334         if (strcmp (operation, "file:test") == 0) {
2335                 *(char **)operation_data = g_strdup ("test ok");
2336                 return GNOME_VFS_OK;
2337         }
2338         return GNOME_VFS_ERROR_NOT_SUPPORTED;
2339 }
2340
2341 static GnomeVFSMethod method = {
2342         sizeof (GnomeVFSMethod),
2343         do_open,
2344         do_create,
2345         do_close,
2346         do_read,
2347         do_write,
2348         do_seek,
2349         do_tell,
2350         do_truncate_handle,
2351         do_open_directory,
2352         do_close_directory,
2353         do_read_directory,
2354         do_get_file_info,
2355         do_get_file_info_from_handle,
2356         do_is_local,
2357         do_make_directory,
2358         do_remove_directory,
2359         do_move,
2360         do_unlink,
2361         do_check_same_fs,
2362         do_set_file_info,
2363         do_truncate,
2364         do_find_directory,
2365         do_create_symbolic_link,
2366         do_monitor_add,
2367         do_monitor_cancel,
2368         do_file_control
2369 };
2370
2371 GnomeVFSMethod *
2372 vfs_module_init (const char *method_name, const char *args)
2373 {
2374         return &method;
2375 }
2376
2377 void
2378 vfs_module_shutdown (GnomeVFSMethod *method)
2379 {
2380 }