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
5 Copyright (C) 1999 Free Software Foundation
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.
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.
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.
23 Ettore Perazzoli <ettore@comm2000.it>
24 Pavel Cisler <pavel@eazel.com>
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>
42 #include <glib/gstrfuncs.h>
43 #include <glib/gutils.h>
49 #include <sys/types.h>
55 #include <glib/giochannel.h>
59 FAMConnection *fam_connection = NULL;
60 G_LOCK_DEFINE_STATIC (fam_connection);
71 #define GET_PATH_MAX() PATH_MAX
76 static unsigned int value;
78 /* This code is copied from GNU make. It returns the maximum
79 path length by using `pathconf'. */
82 long int x = pathconf(G_DIR_SEPARATOR_S, _PC_PATH_MAX);
100 #if defined(HAVE_LSEEK64) && defined(HAVE_OFF64_T)
101 #define LSEEK lseek64
102 #define OFF_T off64_t
109 get_path_from_uri (GnomeVFSURI const *uri)
113 path = gnome_vfs_unescape_string (uri->text,
120 if (path[0] != G_DIR_SEPARATOR) {
129 get_base_from_uri (GnomeVFSURI const *uri)
131 gchar *escaped_base, *base;
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);
145 file_handle_new (GnomeVFSURI *uri,
149 result = g_new (FileHandle, 1);
151 result->uri = gnome_vfs_uri_ref (uri);
158 file_handle_destroy (FileHandle *handle)
160 gnome_vfs_uri_unref (handle->uri);
164 static GnomeVFSResult
165 do_open (GnomeVFSMethod *method,
166 GnomeVFSMethodHandle **method_handle,
168 GnomeVFSOpenMode mode,
169 GnomeVFSContext *context)
171 FileHandle *file_handle;
177 _GNOME_VFS_METHOD_PARAM_CHECK (method_handle != NULL);
178 _GNOME_VFS_METHOD_PARAM_CHECK (uri != NULL);
180 if (mode & GNOME_VFS_OPEN_READ) {
181 if (mode & GNOME_VFS_OPEN_WRITE)
184 unix_mode = O_RDONLY;
186 if (mode & GNOME_VFS_OPEN_WRITE)
187 unix_mode = O_WRONLY;
189 return GNOME_VFS_ERROR_INVALID_OPEN_MODE;
192 if (! (mode & GNOME_VFS_OPEN_RANDOM) && (mode & GNOME_VFS_OPEN_WRITE))
193 unix_mode |= O_TRUNC;
195 file_name = get_path_from_uri (uri);
196 if (file_name == NULL)
197 return GNOME_VFS_ERROR_INVALID_URI;
200 fd = OPEN (file_name, unix_mode);
203 && ! gnome_vfs_context_check_cancellation (context));
208 return gnome_vfs_result_from_errno ();
210 if (fstat (fd, &statbuf) != 0)
211 return gnome_vfs_result_from_errno ();
213 if (S_ISDIR (statbuf.st_mode)) {
215 return GNOME_VFS_ERROR_IS_DIRECTORY;
218 file_handle = file_handle_new (uri, fd);
220 *method_handle = (GnomeVFSMethodHandle *) file_handle;
225 static GnomeVFSResult
226 do_create (GnomeVFSMethod *method,
227 GnomeVFSMethodHandle **method_handle,
229 GnomeVFSOpenMode mode,
232 GnomeVFSContext *context)
234 FileHandle *file_handle;
239 _GNOME_VFS_METHOD_PARAM_CHECK (method_handle != NULL);
240 _GNOME_VFS_METHOD_PARAM_CHECK (uri != NULL);
242 unix_mode = O_CREAT | O_TRUNC;
244 if (!(mode & GNOME_VFS_OPEN_WRITE))
245 return GNOME_VFS_ERROR_INVALID_OPEN_MODE;
247 if (mode & GNOME_VFS_OPEN_READ)
250 unix_mode |= O_WRONLY;
255 file_name = get_path_from_uri (uri);
256 if (file_name == NULL)
257 return GNOME_VFS_ERROR_INVALID_URI;
260 fd = OPEN (file_name, unix_mode, perm);
263 && ! gnome_vfs_context_check_cancellation (context));
268 return gnome_vfs_result_from_errno ();
270 file_handle = file_handle_new (uri, fd);
272 *method_handle = (GnomeVFSMethodHandle *) file_handle;
277 static GnomeVFSResult
278 do_close (GnomeVFSMethod *method,
279 GnomeVFSMethodHandle *method_handle,
280 GnomeVFSContext *context)
282 FileHandle *file_handle;
285 g_return_val_if_fail (method_handle != NULL, GNOME_VFS_ERROR_INTERNAL);
287 file_handle = (FileHandle *) method_handle;
290 close_retval = close (file_handle->fd);
291 while (close_retval != 0
293 && ! gnome_vfs_context_check_cancellation (context));
295 /* FIXME bugzilla.eazel.com 1163: Should do this even after a failure? */
296 file_handle_destroy (file_handle);
298 if (close_retval != 0) {
299 return gnome_vfs_result_from_errno ();
305 static GnomeVFSResult
306 do_read (GnomeVFSMethod *method,
307 GnomeVFSMethodHandle *method_handle,
309 GnomeVFSFileSize num_bytes,
310 GnomeVFSFileSize *bytes_read,
311 GnomeVFSContext *context)
313 FileHandle *file_handle;
316 g_return_val_if_fail (method_handle != NULL, GNOME_VFS_ERROR_INTERNAL);
318 file_handle = (FileHandle *) method_handle;
321 read_val = read (file_handle->fd, buffer, num_bytes);
322 } while (read_val == -1
324 && ! gnome_vfs_context_check_cancellation (context));
326 if (read_val == -1) {
328 return gnome_vfs_result_from_errno ();
330 *bytes_read = read_val;
332 /* Getting 0 from read() means EOF! */
334 return GNOME_VFS_ERROR_EOF;
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)
348 FileHandle *file_handle;
351 g_return_val_if_fail (method_handle != NULL, GNOME_VFS_ERROR_INTERNAL);
353 file_handle = (FileHandle *) method_handle;
356 write_val = write (file_handle->fd, buffer, num_bytes);
357 while (write_val == -1
359 && ! gnome_vfs_context_check_cancellation (context));
361 if (write_val == -1) {
363 return gnome_vfs_result_from_errno ();
365 *bytes_written = write_val;
372 seek_position_to_unix (GnomeVFSSeekPosition position)
375 case GNOME_VFS_SEEK_START:
377 case GNOME_VFS_SEEK_CURRENT:
379 case GNOME_VFS_SEEK_END:
382 g_warning (_("Unknown GnomeVFSSeekPosition %d"), position);
383 return SEEK_SET; /* bogus */
387 static GnomeVFSResult
388 do_seek (GnomeVFSMethod *method,
389 GnomeVFSMethodHandle *method_handle,
390 GnomeVFSSeekPosition whence,
391 GnomeVFSFileOffset offset,
392 GnomeVFSContext *context)
394 FileHandle *file_handle;
397 file_handle = (FileHandle *) method_handle;
398 lseek_whence = seek_position_to_unix (whence);
400 if (LSEEK (file_handle->fd, offset, lseek_whence) == -1) {
402 return GNOME_VFS_ERROR_NOT_SUPPORTED;
404 return gnome_vfs_result_from_errno ();
410 static GnomeVFSResult
411 do_tell (GnomeVFSMethod *method,
412 GnomeVFSMethodHandle *method_handle,
413 GnomeVFSFileOffset *offset_return)
415 FileHandle *file_handle;
418 file_handle = (FileHandle *) method_handle;
420 offset = LSEEK (file_handle->fd, 0, SEEK_CUR);
423 return GNOME_VFS_ERROR_NOT_SUPPORTED;
425 return gnome_vfs_result_from_errno ();
428 *offset_return = offset;
433 static GnomeVFSResult
434 do_truncate_handle (GnomeVFSMethod *method,
435 GnomeVFSMethodHandle *method_handle,
436 GnomeVFSFileSize where,
437 GnomeVFSContext *context)
439 FileHandle *file_handle;
441 g_return_val_if_fail (method_handle != NULL, GNOME_VFS_ERROR_INTERNAL);
443 file_handle = (FileHandle *) method_handle;
445 if (ftruncate (file_handle->fd, where) == 0) {
451 return GNOME_VFS_ERROR_READ_ONLY;
453 return GNOME_VFS_ERROR_NOT_SUPPORTED;
455 return GNOME_VFS_ERROR_GENERIC;
460 static GnomeVFSResult
461 do_truncate (GnomeVFSMethod *method,
463 GnomeVFSFileSize where,
464 GnomeVFSContext *context)
468 path = get_path_from_uri (uri);
470 return GNOME_VFS_ERROR_INVALID_URI;
472 if (truncate (path, where) == 0) {
480 return GNOME_VFS_ERROR_READ_ONLY;
482 return GNOME_VFS_ERROR_NOT_SUPPORTED;
484 return GNOME_VFS_ERROR_GENERIC;
492 GnomeVFSFileInfoOptions options;
494 struct dirent *current_entry;
500 static DirectoryHandle *
501 directory_handle_new (GnomeVFSURI *uri,
503 GnomeVFSFileInfoOptions options)
505 DirectoryHandle *result;
509 result = g_new (DirectoryHandle, 1);
511 result->uri = gnome_vfs_uri_ref (uri);
514 /* Reserve extra space for readdir_r, see man page */
515 result->current_entry = g_malloc (sizeof (struct dirent) + GET_PATH_MAX() + 1);
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);
521 result->name_buffer = g_malloc (full_name_len + GET_PATH_MAX () + 2);
522 memcpy (result->name_buffer, full_name, full_name_len);
524 if (full_name_len > 0 && full_name[full_name_len - 1] != '/')
525 result->name_buffer[full_name_len++] = '/';
527 result->name_ptr = result->name_buffer + full_name_len;
531 result->options = options;
537 directory_handle_destroy (DirectoryHandle *directory_handle)
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);
545 /* MIME detection code. */
547 get_mime_type (GnomeVFSFileInfo *info,
548 const char *full_name,
549 GnomeVFSFileInfoOptions options,
550 struct stat *stat_buffer)
552 const char *mime_type;
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
560 mime_type = "x-special/symlink";
562 mime_type = gnome_vfs_get_file_mime_type (full_name,
563 stat_buffer, (options & GNOME_VFS_FILE_INFO_FORCE_FAST_MIME_TYPE) != 0);
566 g_assert (mime_type);
567 info->mime_type = g_strdup (mime_type);
568 info->valid_fields |= GNOME_VFS_FILE_INFO_FIELDS_MIME_TYPE;
572 read_link (const gchar *full_name)
578 buffer = g_malloc (size);
583 read_size = readlink (full_name, buffer, size);
588 if (read_size < size) {
589 buffer[read_size] = 0;
593 buffer = g_realloc (buffer, size);
598 get_access_info (GnomeVFSFileInfo *file_info,
599 const gchar *full_name)
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
605 if (access (full_name, R_OK) == 0) {
606 file_info->permissions |= GNOME_VFS_PERM_ACCESS_READABLE;
609 if (access (full_name, W_OK) == 0) {
610 file_info->permissions |= GNOME_VFS_PERM_ACCESS_WRITABLE;
613 if (access (full_name, X_OK) == 0) {
614 file_info->permissions |= GNOME_VFS_PERM_ACCESS_EXECUTABLE;
616 file_info->valid_fields |= GNOME_VFS_FILE_INFO_FIELDS_ACCESS;
619 static GnomeVFSResult
620 get_stat_info (GnomeVFSFileInfo *file_info,
621 const gchar *full_name,
622 GnomeVFSFileInfoOptions options,
623 struct stat *statptr)
626 gboolean followed_symlink;
629 char *link_file_path;
634 followed_symlink = FALSE;
638 GNOME_VFS_FILE_INFO_SET_LOCAL (file_info, TRUE);
640 if (statptr == NULL) {
644 if (lstat (full_name, statptr) != 0) {
645 return gnome_vfs_result_from_errno ();
648 is_symlink = S_ISLNK (statptr->st_mode);
650 if ((options & GNOME_VFS_FILE_INFO_FOLLOW_LINKS) && is_symlink) {
651 if (stat (full_name, statptr) != 0) {
652 if (errno == ELOOP) {
656 /* It's a broken symlink, revert to the lstat. This is sub-optimal but
657 * acceptable because it's not a common case.
659 if (lstat (full_name, statptr) != 0) {
660 return gnome_vfs_result_from_errno ();
663 GNOME_VFS_FILE_INFO_SET_SYMLINK (file_info, TRUE);
664 followed_symlink = TRUE;
667 gnome_vfs_stat_to_file_info (file_info, statptr);
671 link_file_path = g_strdup (full_name);
673 /* We will either successfully read the link name or return
674 * NULL if read_link fails -- flag it as a valid field either
677 file_info->valid_fields |= GNOME_VFS_FILE_INFO_FIELDS_SYMLINK_NAME;
680 /* Deal with multiple-level symlinks by following them as
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 ();
690 if (symlink_name[0] != '/') {
691 symlink_dir = g_path_get_dirname (link_file_path);
692 newpath = g_build_filename (symlink_dir,
694 g_free (symlink_dir);
695 g_free (symlink_name);
696 symlink_name = newpath;
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 */
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 */
709 g_free (link_file_path);
710 link_file_path = g_strdup (symlink_name);
712 g_free (link_file_path);
714 file_info->symlink_name = symlink_name;
720 static GnomeVFSResult
721 get_stat_info_from_handle (GnomeVFSFileInfo *file_info,
723 GnomeVFSFileInfoOptions options,
724 struct stat *statptr)
728 if (statptr == NULL) {
732 if (fstat (handle->fd, statptr) != 0) {
733 return gnome_vfs_result_from_errno ();
736 gnome_vfs_stat_to_file_info (file_info, statptr);
737 GNOME_VFS_FILE_INFO_SET_LOCAL (file_info, TRUE);
743 static GnomeVFSResult
744 do_open_directory (GnomeVFSMethod *method,
745 GnomeVFSMethodHandle **method_handle,
747 GnomeVFSFileInfoOptions options,
748 GnomeVFSContext *context)
750 gchar *directory_name;
753 directory_name = get_path_from_uri (uri);
754 if (directory_name == NULL)
755 return GNOME_VFS_ERROR_INVALID_URI;
757 dir = opendir (directory_name);
758 g_free (directory_name);
760 return gnome_vfs_result_from_errno ();
763 = (GnomeVFSMethodHandle *) directory_handle_new (uri, dir,
769 static GnomeVFSResult
770 do_close_directory (GnomeVFSMethod *method,
771 GnomeVFSMethodHandle *method_handle,
772 GnomeVFSContext *context)
774 DirectoryHandle *directory_handle;
776 directory_handle = (DirectoryHandle *) method_handle;
778 closedir (directory_handle->dir);
780 directory_handle_destroy (directory_handle);
785 #ifndef HAVE_READDIR_R
786 G_LOCK_DEFINE_STATIC (readdir);
789 static GnomeVFSResult
790 do_read_directory (GnomeVFSMethod *method,
791 GnomeVFSMethodHandle *method_handle,
792 GnomeVFSFileInfo *file_info,
793 GnomeVFSContext *context)
795 struct dirent *result;
798 DirectoryHandle *handle;
800 handle = (DirectoryHandle *) method_handle;
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.
809 return GNOME_VFS_ERROR_EOF;
811 return gnome_vfs_result_from_errno ();
816 result = readdir (handle->dir);
818 if (result == NULL && errno != 0) {
819 GnomeVFSResult ret = gnome_vfs_result_from_errno ();
823 if (result != NULL) {
824 memcpy (handle->current_entry, result, sizeof (struct dirent));
829 if (result == NULL) {
830 return GNOME_VFS_ERROR_EOF;
833 file_info->name = g_strdup (result->d_name);
835 strcpy (handle->name_ptr, result->d_name);
836 full_name = handle->name_buffer;
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
846 if (handle->options & GNOME_VFS_FILE_INFO_GET_MIME_TYPE) {
847 get_mime_type (file_info, full_name, handle->options, &statbuf);
853 static GnomeVFSResult
854 do_get_file_info (GnomeVFSMethod *method,
856 GnomeVFSFileInfo *file_info,
857 GnomeVFSFileInfoOptions options,
858 GnomeVFSContext *context)
860 GnomeVFSResult result;
864 full_name = get_path_from_uri (uri);
865 if (full_name == NULL)
866 return GNOME_VFS_ERROR_INVALID_URI;
868 file_info->valid_fields = GNOME_VFS_FILE_INFO_FIELDS_NONE;
870 file_info->name = get_base_from_uri (uri);
871 g_assert (file_info->name != NULL);
873 result = get_stat_info (file_info, full_name, options, &statbuf);
874 if (result != GNOME_VFS_OK) {
879 if (options & GNOME_VFS_FILE_INFO_GET_ACCESS_RIGHTS) {
880 get_access_info (file_info, full_name);
883 if (options & GNOME_VFS_FILE_INFO_GET_MIME_TYPE) {
884 get_mime_type (file_info, full_name, options, &statbuf);
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)
899 FileHandle *file_handle;
902 GnomeVFSResult result;
904 file_handle = (FileHandle *) method_handle;
906 file_info->valid_fields = GNOME_VFS_FILE_INFO_FIELDS_NONE;
908 full_name = get_path_from_uri (file_handle->uri);
909 if (full_name == NULL) {
910 return GNOME_VFS_ERROR_INVALID_URI;
913 file_info->name = get_base_from_uri (file_handle->uri);
914 g_assert (file_info->name != NULL);
916 result = get_stat_info_from_handle (file_info, file_handle,
918 if (result != GNOME_VFS_OK) {
923 if (options & GNOME_VFS_FILE_INFO_GET_MIME_TYPE) {
924 get_mime_type (file_info, full_name, options, &statbuf);
932 GHashTable *fstype_hash = NULL;
933 G_LOCK_DEFINE_STATIC (fstype_hash);
934 extern char *filesystem_type (char *path, char *relpath, struct stat *statp);
937 do_is_local (GnomeVFSMethod *method,
938 const GnomeVFSURI *uri)
941 gpointer local = NULL;
943 g_return_val_if_fail (uri != NULL, FALSE);
945 path = get_path_from_uri (uri);
947 return TRUE; /* GNOME_VFS_ERROR_INVALID_URI */
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);
954 local = g_hash_table_lookup (fstype_hash, path);
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);
969 G_UNLOCK (fstype_hash);
970 return GPOINTER_TO_INT (local) > 0;
974 static GnomeVFSResult
975 do_make_directory (GnomeVFSMethod *method,
978 GnomeVFSContext *context)
983 full_name = get_path_from_uri (uri);
984 if (full_name == NULL)
985 return GNOME_VFS_ERROR_INVALID_URI;
987 retval = mkdir (full_name, perm);
992 return gnome_vfs_result_from_errno ();
998 static GnomeVFSResult
999 do_remove_directory (GnomeVFSMethod *method,
1001 GnomeVFSContext *context)
1006 full_name = get_path_from_uri (uri);
1007 if (full_name == NULL)
1008 return GNOME_VFS_ERROR_INVALID_URI;
1010 retval = rmdir (full_name);
1015 return gnome_vfs_result_from_errno ();
1018 return GNOME_VFS_OK;
1021 #undef DEBUG_FIND_DIRECTORY
1022 /* Get rid of debugging code once we know the logic works. */
1024 #define TRASH_DIRECTORY_NAME_BASE ".Trash"
1025 #define MAX_TRASH_SEARCH_DEPTH 5
1028 * Works like mkdir, except it creates all the levels of directories in @path.
1031 mkdir_recursive (const char *path, int permission_bits)
1033 struct stat stat_buffer;
1034 const char *dir_separator_scanner;
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) {
1044 if (*dir_separator_scanner == G_DIR_SEPARATOR) {
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;
1055 g_free (current_path);
1058 g_free (current_path);
1060 if (!*dir_separator_scanner) {
1069 append_to_path (const char *path, const char *name)
1071 return g_strconcat (path, G_DIR_SEPARATOR_S, name, NULL);
1075 append_trash_path (const char *path)
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.
1081 if (strcmp (path, "/") == 0) {
1082 return g_strconcat (path, TRASH_DIRECTORY_NAME_BASE,
1083 "-", g_get_user_name (), NULL);
1085 return g_strconcat (path, G_DIR_SEPARATOR_S, TRASH_DIRECTORY_NAME_BASE,
1086 "-", g_get_user_name (), NULL);
1090 /* Try to find the Trash in @current_directory. If not found, collect all the
1091 * directories in @current_directory to visit later.
1094 find_trash_in_one_hierarchy_level (const char *current_directory, dev_t near_device_id,
1095 GList **directory_list, GnomeVFSContext *context)
1099 struct stat stat_buffer;
1101 struct dirent *item_buffer;
1102 struct dirent *item;
1104 if (gnome_vfs_context_check_cancellation (context))
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);
1114 g_free (trash_path);
1117 if (gnome_vfs_context_check_cancellation (context))
1120 /* Trash not in this directory.
1121 * Collect the list of all the directories in this directory to visit later.
1123 directory = opendir (current_directory);
1124 if (directory == NULL) {
1128 item_buffer = g_malloc (sizeof (struct dirent) + GET_PATH_MAX() + 1);
1130 #ifdef HAVE_READDIR_R
1131 if (readdir_r (directory, item_buffer, &item) != 0 || item == NULL) {
1136 item = readdir (directory);
1143 if (gnome_vfs_context_check_cancellation (context)) {
1144 #ifndef HAVE_READDIR_R
1150 if (strcmp (item->d_name, ".") == 0
1151 || strcmp (item->d_name, "..") == 0) {
1152 #ifndef HAVE_READDIR_R
1158 item_path = append_to_path (current_directory, item->d_name);
1159 #ifndef HAVE_READDIR_R
1162 if (lstat (item_path, &stat_buffer) == 0
1163 && S_ISDIR (stat_buffer.st_mode)
1164 && near_device_id == stat_buffer.st_dev) {
1166 /* Directory -- put it on the list to search,
1167 * just as long as it is on the same device.
1169 *directory_list = g_list_prepend (*directory_list, item_path);
1173 if (gnome_vfs_context_check_cancellation (context))
1178 closedir (directory);
1179 g_free (item_buffer);
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.
1188 * We only look MAX_TRASH_SEARCH_DEPTH deep, if the Trash is deeper in the hierarchy,
1189 * we will fail to find it.
1192 find_trash_in_hierarchy (const char *start_dir, dev_t near_device_id, GnomeVFSContext *context)
1194 GList *next_directory_list;
1197 #ifdef DEBUG_FIND_DIRECTORY
1198 g_print ("searching for trash in %s\n", start_dir);
1201 next_directory_list = NULL;
1203 /* Search the top level. */
1204 result = find_trash_in_one_hierarchy_level (start_dir, near_device_id,
1205 &next_directory_list, context);
1207 gnome_vfs_list_deep_free (next_directory_list);
1212 static GList *cached_trash_directories;
1213 G_LOCK_DEFINE_STATIC (cached_trash_directories);
1215 /* Element used to store chached Trash entries in the local, in-memory Trash item cache. */
1218 char *device_mount_point;
1220 } TrashDirectoryCachedItem;
1224 } FindByDeviceIDParameters;
1227 match_trash_item_by_device_id (gconstpointer item, gconstpointer data)
1229 const TrashDirectoryCachedItem *cached_item;
1230 FindByDeviceIDParameters *parameters;
1232 cached_item = (const TrashDirectoryCachedItem *)item;
1233 parameters = (FindByDeviceIDParameters *)data;
1235 return cached_item->device_id == parameters->device_id ? 0 : -1;
1239 try_creating_trash_in (const char *path, guint permissions)
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);
1252 #ifdef DEBUG_FIND_DIRECTORY
1253 g_print ("failed to create trash in %s\n", trash_path);
1255 g_free (trash_path);
1260 find_disk_top_directory (const char *item_on_disk,
1261 dev_t near_device_id,
1262 GnomeVFSContext *context)
1264 char *disk_top_directory;
1265 struct stat stat_buffer;
1267 disk_top_directory = g_strdup (item_on_disk);
1269 /* Walk up in the hierarchy, finding the top-most point that still
1270 * matches our device ID -- the root directory of the volume.
1273 char *previous_search_directory;
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);
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;
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.
1296 if (gnome_vfs_context_check_cancellation (context)) {
1297 g_free (previous_search_directory);
1298 g_free (disk_top_directory);
1302 return disk_top_directory;
1305 #define TRASH_ENTRY_CACHE_PARENT ".gnome/gnome-vfs"
1306 #define TRASH_ENTRY_CACHE_NAME ".trash_entry_cache"
1307 #define NON_EXISTENT_TRASH_ENTRY "-"
1309 /* Save the localy cached Trashed paths on disk in the user's home
1313 save_trash_entry_cache (void)
1316 char *cache_file_parent, *cache_file_path;
1318 char *buffer, *escaped_path, *escaped_mount_point;
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);
1323 if (mkdir_recursive (cache_file_parent, 0777) != 0) {
1324 g_warning ("failed to create trash item cache file");
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");
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);
1341 buffer = g_strdup_printf ("%s %s\n", escaped_mount_point, escaped_path);
1342 write (cache_file, buffer, strlen (buffer));
1344 #ifdef DEBUG_FIND_DIRECTORY
1345 g_print ("saving trash item cache %s\n", buffer);
1349 g_free (escaped_mount_point);
1350 g_free (escaped_path);
1354 g_free (cache_file_path);
1355 g_free (cache_file_parent);
1359 const char *mount_point;
1360 const char *trash_path;
1363 } UpdateOneCachedEntryContext;
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.
1369 update_one_cached_trash_entry (gpointer element, gpointer cast_to_context)
1371 UpdateOneCachedEntryContext *context;
1372 TrashDirectoryCachedItem *item;
1374 context = (UpdateOneCachedEntryContext *)cast_to_context;
1375 item = (TrashDirectoryCachedItem *)element;
1377 if (context->done) {
1378 /* We already took care of business in a previous iteration. */
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;
1389 context->done = TRUE;
1394 add_local_cached_trash_entry (dev_t near_device_id, const char *trash_path, const char *mount_point)
1396 TrashDirectoryCachedItem *new_cached_item;
1397 UpdateOneCachedEntryContext update_context;
1399 /* First check if we already have an entry for this mountpoint,
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;
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. */
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;
1421 cached_trash_directories = g_list_prepend (cached_trash_directories, new_cached_item);
1425 add_cached_trash_entry (dev_t near_device_id, const char *trash_path, const char *mount_point)
1427 add_local_cached_trash_entry (near_device_id, trash_path, mount_point);
1428 /* write out the local cache */
1429 save_trash_entry_cache ();
1433 destroy_cached_trash_entry (TrashDirectoryCachedItem *entry)
1435 g_free (entry->path);
1436 g_free (entry->device_mount_point);
1440 /* Read the cached entries for the file cache into the local Trash item cache. */
1442 read_saved_cached_trash_entries (void)
1444 char *cache_file_path;
1447 char escaped_mount_point[PATH_MAX], escaped_trash_path[PATH_MAX];
1448 char *mount_point, *trash_path;
1449 struct stat stat_buffer;
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;
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");
1462 if (cache_file != NULL) {
1464 if (fgets (buffer, sizeof (buffer), cache_file) == 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, "/");
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.
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);
1489 g_free (trash_path);
1490 g_free (mount_point);
1492 fclose (cache_file);
1495 g_free (cache_file_path);
1498 /* Create a Trash directory on the same disk as @full_name_near. */
1500 create_trash_near (const char *full_name_near, dev_t near_device_id, const char *disk_top_directory,
1501 guint permissions, GnomeVFSContext *context)
1503 return try_creating_trash_in (disk_top_directory, permissions);
1508 cached_trash_entry_exists (const TrashDirectoryCachedItem *entry)
1510 struct stat stat_buffer;
1511 return lstat (entry->path, &stat_buffer) == 0;
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.
1518 find_locally_cached_trash_entry_for_device_id (dev_t device_id, gboolean check_disk)
1520 GList *matching_item;
1521 FindByDeviceIDParameters tmp;
1522 const char *trash_path;
1524 tmp.device_id = device_id;
1526 matching_item = g_list_find_custom (cached_trash_directories,
1527 &tmp, match_trash_item_by_device_id);
1529 if (matching_item == NULL) {
1533 trash_path = ((TrashDirectoryCachedItem *)matching_item->data)->path;
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);
1540 return g_strdup (NON_EXISTENT_TRASH_ENTRY);
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
1550 #ifdef DEBUG_FIND_DIRECTORY
1551 g_print ("entry %s doesn't exist, removing \n",
1552 ((TrashDirectoryCachedItem *)matching_item->data)->path);
1554 destroy_cached_trash_entry ((TrashDirectoryCachedItem *)matching_item->data);
1555 cached_trash_directories = g_list_remove (cached_trash_directories,
1556 matching_item->data);
1561 #ifdef DEBUG_FIND_DIRECTORY
1562 g_print ("local cache found %s \n", trash_path);
1564 g_assert (matching_item != NULL);
1565 return g_strdup (trash_path);
1568 /* Look for an entry in the file and local caches. */
1570 find_cached_trash_entry_for_device (dev_t device_id, gboolean check_disk)
1572 if (cached_trash_directories == NULL) {
1576 read_saved_cached_trash_entries ();
1578 return find_locally_cached_trash_entry_for_device_id (device_id, check_disk);
1581 /* Search for a Trash entry or create one. Called when there is no cached entry. */
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)
1588 char *disk_top_directory;
1591 /* figure out the topmost disk directory */
1592 disk_top_directory = find_disk_top_directory (full_name_near,
1593 near_device_id, context);
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.
1599 #ifdef DEBUG_FIND_DIRECTORY
1600 g_print ("failed to find top disk directory for %s\n", full_name_near);
1602 add_cached_trash_entry (near_device_id, NON_EXISTENT_TRASH_ENTRY, disk_top_directory);
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.
1613 result = g_strdup(NON_EXISTENT_TRASH_ENTRY);
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);
1623 if (result != NULL) {
1624 /* remember whatever we found for next time */
1625 add_cached_trash_entry (near_device_id, result, disk_top_directory);
1628 g_free (disk_top_directory);
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.
1636 * This is the only entry point for the trash cache code,
1637 * we holds the lock while operating on it only here.
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)
1646 G_LOCK (cached_trash_directories);
1648 /* look in the saved trash locations first */
1649 result = find_cached_trash_entry_for_device (near_device_id, find_if_needed);
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.
1659 #ifdef DEBUG_FIND_DIRECTORY
1660 g_print ("cache indicates no trash for %s, force a creation \n", full_name_near);
1666 if (result == NULL) {
1667 /* No luck sofar. Look for the Trash on the disk, optionally create it
1668 * if we find nothing.
1670 result = find_or_create_trash_near (full_name_near, near_device_id,
1671 create_if_needed, find_if_needed, permissions, context);
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);
1680 if (result != NULL && strcmp(result, NON_EXISTENT_TRASH_ENTRY) == 0) {
1681 /* This means that we know there is no Trash */
1686 G_UNLOCK (cached_trash_directories);
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,
1699 GnomeVFSContext *context)
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;
1710 target_directory_path = NULL;
1713 full_name_near = get_path_from_uri (near_uri);
1714 if (full_name_near == NULL)
1715 return GNOME_VFS_ERROR_INVALID_URI;
1717 /* We will need the URI and the stat structure for the home directory. */
1718 home_directory = g_get_home_dir ();
1720 if (gnome_vfs_context_check_cancellation (context)) {
1721 g_free (full_name_near);
1722 return GNOME_VFS_ERROR_CANCELLED;
1725 retval = lstat (full_name_near, &near_item_stat);
1727 g_free (full_name_near);
1728 return gnome_vfs_result_from_errno ();
1731 if (gnome_vfs_context_check_cancellation (context)) {
1732 g_free (full_name_near);
1733 return GNOME_VFS_ERROR_CANCELLED;
1736 retval = stat (home_directory, &home_volume_stat);
1738 g_free (full_name_near);
1739 return gnome_vfs_result_from_errno ();
1742 if (gnome_vfs_context_check_cancellation (context)) {
1743 g_free (full_name_near);
1744 return GNOME_VFS_ERROR_CANCELLED;
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.
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.
1758 FindByDeviceIDParameters tmp;
1759 tmp.device_id = near_item_stat.st_dev;
1761 if (gnome_vfs_context_check_cancellation (context))
1762 return GNOME_VFS_ERROR_CANCELLED;
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);
1768 if (gnome_vfs_context_check_cancellation (context)) {
1769 return GNOME_VFS_ERROR_CANCELLED;
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);
1777 case GNOME_VFS_DIRECTORY_KIND_DESKTOP:
1778 if (near_item_stat.st_dev != home_volume_stat.st_dev) {
1782 target_directory_path = append_to_path (home_directory, "Desktop");
1789 g_free (full_name_near);
1791 if (target_directory_path == NULL) {
1792 return GNOME_VFS_ERROR_NOT_SUPPORTED;
1795 if (create_if_needed && access (target_directory_path, F_OK) != 0) {
1796 mkdir_recursive (target_directory_path, permissions);
1799 if (access (target_directory_path, F_OK) != 0) {
1800 g_free (target_directory_path);
1801 return GNOME_VFS_ERROR_NOT_FOUND;
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);
1809 return GNOME_VFS_OK;
1812 static GnomeVFSResult
1813 rename_helper (const gchar *old_full_name,
1814 const gchar *new_full_name,
1815 gboolean force_replace,
1816 GnomeVFSContext *context)
1818 gboolean old_exists;
1819 struct stat statbuf;
1822 retval = stat (new_full_name, &statbuf);
1824 /* If we are not allowed to replace an existing file, return an
1826 if (! force_replace)
1827 return GNOME_VFS_ERROR_FILE_EXISTS;
1833 if (gnome_vfs_context_check_cancellation (context))
1834 return GNOME_VFS_ERROR_CANCELLED;
1836 retval = rename (old_full_name, new_full_name);
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
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);
1855 return gnome_vfs_result_from_errno ();
1858 if (gnome_vfs_context_check_cancellation (context))
1859 return GNOME_VFS_ERROR_CANCELLED;
1861 retval = rename (old_full_name, new_full_name);
1866 return gnome_vfs_result_from_errno ();
1869 return GNOME_VFS_OK;
1872 static GnomeVFSResult
1873 do_move (GnomeVFSMethod *method,
1874 GnomeVFSURI *old_uri,
1875 GnomeVFSURI *new_uri,
1876 gboolean force_replace,
1877 GnomeVFSContext *context)
1879 gchar *old_full_name;
1880 gchar *new_full_name;
1881 GnomeVFSResult result;
1883 old_full_name = get_path_from_uri (old_uri);
1884 if (old_full_name == NULL)
1885 return GNOME_VFS_ERROR_INVALID_URI;
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;
1893 result = rename_helper (old_full_name, new_full_name,
1894 force_replace, context);
1896 g_free (old_full_name);
1897 g_free (new_full_name);
1902 static GnomeVFSResult
1903 do_unlink (GnomeVFSMethod *method,
1905 GnomeVFSContext *context)
1910 full_name = get_path_from_uri (uri);
1911 if (full_name == NULL) {
1912 return GNOME_VFS_ERROR_INVALID_URI;
1915 retval = unlink (full_name);
1920 return gnome_vfs_result_from_errno ();
1923 return GNOME_VFS_OK;
1926 static GnomeVFSResult
1927 do_create_symbolic_link (GnomeVFSMethod *method,
1929 const char *target_reference,
1930 GnomeVFSContext *context)
1932 const char *link_scheme, *target_scheme;
1933 char *link_full_name, *target_full_name;
1934 GnomeVFSResult result;
1935 GnomeVFSURI *target_uri;
1937 g_assert (target_reference != NULL);
1938 g_assert (uri != NULL);
1940 /* what we actually want is a function that takes a const char * and
1941 * tells whether it is a valid URI
1943 target_uri = gnome_vfs_uri_new (target_reference);
1944 if (target_uri == NULL) {
1945 return GNOME_VFS_ERROR_INVALID_URI;
1948 link_scheme = gnome_vfs_uri_get_scheme (uri);
1949 g_assert (link_scheme != NULL);
1951 target_scheme = gnome_vfs_uri_get_scheme (target_uri);
1952 if (target_scheme == NULL) {
1953 target_scheme = "file";
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);
1962 target_full_name = get_path_from_uri (target_uri);
1965 link_full_name = get_path_from_uri (uri);
1967 if (symlink (target_full_name, link_full_name) != 0) {
1968 result = gnome_vfs_result_from_errno ();
1970 result = GNOME_VFS_OK;
1973 g_free (target_full_name);
1974 g_free (link_full_name);
1976 /* FIXME bugzilla.eazel.com 2792: do a URI link */
1977 result = GNOME_VFS_ERROR_NOT_SUPPORTED;
1980 gnome_vfs_uri_unref (target_uri);
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)
1999 gchar *full_name_source, *full_name_target;
2000 struct stat s_source, s_target;
2003 full_name_source = get_path_from_uri (source_uri);
2004 retval = lstat (full_name_source, &s_source);
2005 g_free (full_name_source);
2008 return gnome_vfs_result_from_errno ();
2010 if (gnome_vfs_context_check_cancellation (context))
2011 return GNOME_VFS_ERROR_CANCELLED;
2013 full_name_target = get_path_from_uri (target_uri);
2014 retval = stat (full_name_target, &s_target);
2015 g_free (full_name_target);
2018 return gnome_vfs_result_from_errno ();
2020 *same_fs_return = (s_source.st_dev == s_target.st_dev);
2022 return GNOME_VFS_OK;
2025 static GnomeVFSResult
2026 do_set_file_info (GnomeVFSMethod *method,
2028 const GnomeVFSFileInfo *info,
2029 GnomeVFSSetFileInfoMask mask,
2030 GnomeVFSContext *context)
2034 full_name = get_path_from_uri (uri);
2035 if (full_name == NULL)
2036 return GNOME_VFS_ERROR_INVALID_URI;
2038 if (mask & GNOME_VFS_SET_FILE_INFO_NAME) {
2039 GnomeVFSResult result;
2040 gchar *dir, *encoded_dir;
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);
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.
2053 if (dir[strlen(dir) - 1] != '/') {
2054 new_name = g_strconcat (dir, "/", info->name, NULL);
2056 new_name = g_strconcat (dir, info->name, NULL);
2059 result = rename_helper (full_name, new_name, FALSE, context);
2064 if (result != GNOME_VFS_OK) {
2070 if (gnome_vfs_context_check_cancellation (context)) {
2072 return GNOME_VFS_ERROR_CANCELLED;
2075 if (mask & GNOME_VFS_SET_FILE_INFO_PERMISSIONS) {
2076 if (chmod (full_name, info->permissions) != 0) {
2078 return gnome_vfs_result_from_errno ();
2082 if (gnome_vfs_context_check_cancellation (context)) {
2084 return GNOME_VFS_ERROR_CANCELLED;
2087 if (mask & GNOME_VFS_SET_FILE_INFO_OWNER) {
2088 if (chown (full_name, info->uid, info->gid) != 0) {
2090 return gnome_vfs_result_from_errno ();
2094 if (gnome_vfs_context_check_cancellation (context)) {
2096 return GNOME_VFS_ERROR_CANCELLED;
2099 if (mask & GNOME_VFS_SET_FILE_INFO_TIME) {
2100 struct utimbuf utimbuf;
2102 utimbuf.actime = info->atime;
2103 utimbuf.modtime = info->mtime;
2105 if (utime (full_name, &utimbuf) != 0) {
2107 return gnome_vfs_result_from_errno ();
2113 return GNOME_VFS_OK;
2118 fam_do_iter_unlocked (void)
2120 while (fam_connection != NULL && FAMPending(fam_connection)) {
2122 FileMonitorHandle *handle;
2124 GnomeVFSMonitorEventType event_type;
2126 if (FAMNextEvent(fam_connection, &ev) != 1) {
2127 FAMClose(fam_connection);
2128 g_free(fam_connection);
2129 fam_connection = NULL;
2133 handle = (FileMonitorHandle *)ev.userdata;
2134 cancelled = handle->cancelled;
2139 event_type = GNOME_VFS_MONITOR_EVENT_CHANGED;
2142 event_type = GNOME_VFS_MONITOR_EVENT_DELETED;
2144 case FAMStartExecuting:
2145 event_type = GNOME_VFS_MONITOR_EVENT_STARTEXECUTING;
2147 case FAMStopExecuting:
2148 event_type = GNOME_VFS_MONITOR_EVENT_STOPEXECUTING;
2151 event_type = GNOME_VFS_MONITOR_EVENT_CREATED;
2153 case FAMAcknowledge:
2154 if (handle->cancelled) {
2155 gnome_vfs_uri_unref (handle->uri);
2166 if (event_type != -1 && !cancelled) {
2167 GnomeVFSURI *info_uri;
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 '/'.
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);
2180 info_uri = gnome_vfs_uri_append_file_name (handle->uri, ev.filename);
2182 /* This queues an idle, so there are no reentrancy issues */
2183 gnome_vfs_monitor_callback ((GnomeVFSMethodHandle *)handle,
2186 gnome_vfs_uri_unref (info_uri);
2194 fam_callback (GIOChannel *source,
2195 GIOCondition condition,
2199 G_LOCK (fam_connection);
2201 res = fam_do_iter_unlocked ();
2203 G_UNLOCK (fam_connection);
2211 monitor_setup (void)
2216 G_LOCK (fam_connection);
2218 if (fam_connection == NULL) {
2219 fam_connection = g_malloc0(sizeof(FAMConnection));
2220 if (FAMOpen2(fam_connection, "test-monitor") != 0) {
2222 g_print ("FAMOpen failed, FAMErrno=%d\n", FAMErrno);
2224 g_free(fam_connection);
2225 fam_connection = NULL;
2226 G_UNLOCK (fam_connection);
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);
2236 G_UNLOCK (fam_connection);
2242 static GnomeVFSResult
2243 do_monitor_add (GnomeVFSMethod *method,
2244 GnomeVFSMethodHandle **method_handle_return,
2246 GnomeVFSMonitorType monitor_type)
2249 FileMonitorHandle *handle;
2252 if (!monitor_setup ()) {
2253 return GNOME_VFS_ERROR_NOT_SUPPORTED;
2256 handle = g_new0 (FileMonitorHandle, 1);
2258 handle->cancelled = FALSE;
2259 gnome_vfs_uri_ref (uri);
2260 filename = get_path_from_uri (uri);
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 ();
2267 if (fam_connection == NULL) {
2268 G_UNLOCK (fam_connection);
2269 return GNOME_VFS_ERROR_NOT_SUPPORTED;
2272 if (monitor_type == GNOME_VFS_MONITOR_FILE) {
2273 FAMMonitorFile (fam_connection, filename,
2274 &handle->request, handle);
2276 FAMMonitorDirectory (fam_connection, filename,
2277 &handle->request, handle);
2280 G_UNLOCK (fam_connection);
2282 *method_handle_return = (GnomeVFSMethodHandle *)handle;
2286 return GNOME_VFS_OK;
2288 return GNOME_VFS_ERROR_NOT_SUPPORTED;
2292 static GnomeVFSResult
2293 do_monitor_cancel (GnomeVFSMethod *method,
2294 GnomeVFSMethodHandle *method_handle)
2297 FileMonitorHandle *handle = (FileMonitorHandle *)method_handle;
2299 if (!monitor_setup ()) {
2300 return GNOME_VFS_ERROR_NOT_SUPPORTED;
2303 if (handle->cancelled)
2304 return GNOME_VFS_OK;
2306 handle->cancelled = TRUE;
2307 G_LOCK (fam_connection);
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 ();
2313 if (fam_connection == NULL) {
2314 G_UNLOCK (fam_connection);
2315 return GNOME_VFS_ERROR_NOT_SUPPORTED;
2318 FAMCancelMonitor (fam_connection, &handle->request);
2319 G_UNLOCK (fam_connection);
2321 return GNOME_VFS_OK;
2323 return GNOME_VFS_ERROR_NOT_SUPPORTED;
2327 static GnomeVFSResult
2328 do_file_control (GnomeVFSMethod *method,
2329 GnomeVFSMethodHandle *method_handle,
2330 const char *operation,
2331 gpointer operation_data,
2332 GnomeVFSContext *context)
2334 if (strcmp (operation, "file:test") == 0) {
2335 *(char **)operation_data = g_strdup ("test ok");
2336 return GNOME_VFS_OK;
2338 return GNOME_VFS_ERROR_NOT_SUPPORTED;
2341 static GnomeVFSMethod method = {
2342 sizeof (GnomeVFSMethod),
2355 do_get_file_info_from_handle,
2358 do_remove_directory,
2365 do_create_symbolic_link,
2372 vfs_module_init (const char *method_name, const char *args)
2378 vfs_module_shutdown (GnomeVFSMethod *method)