diff options
author | Alexander Larsson <alexl@redhat.com> | 2007-10-04 14:08:11 +0000 |
---|---|---|
committer | Alexander Larsson <alexl@src.gnome.org> | 2007-10-04 14:08:11 +0000 |
commit | efd8ab55dd85159397c043b03efe54d47c28c307 (patch) | |
tree | 870cad51a8485bf4ab0b3c3d756138aa3ab70193 /daemon/gvfsbackendtrash.c | |
parent | 14f210480005deb8a9cb4e23d07f8ba979ee29bb (diff) | |
download | gvfs-efd8ab55dd85159397c043b03efe54d47c28c307.tar.gz |
Allow NULL end pointer
2007-10-04 Alexander Larsson <alexl@redhat.com>
* common/gvfsuriutils.c:
(g_uri_unescape_string):
Allow NULL end pointer
* daemon/gvfsjob.[ch]:
Add cancellable to GVfsJob for backends that want one
* daemon/gvfsjobenumerate.[ch]:
* daemon/gvfsjobqueryinfo.[ch]:
Also store attribute in string form.
Needed if you want to pass it on into gio.
* daemon/Makefile.am:
* daemon/gvfsbackendtrash.[ch]:
* daemon/trash.mount.in: Added.
Added trash backend
svn path=/trunk/; revision=968
Diffstat (limited to 'daemon/gvfsbackendtrash.c')
-rw-r--r-- | daemon/gvfsbackendtrash.c | 806 |
1 files changed, 806 insertions, 0 deletions
diff --git a/daemon/gvfsbackendtrash.c b/daemon/gvfsbackendtrash.c new file mode 100644 index 00000000..6e1215e3 --- /dev/null +++ b/daemon/gvfsbackendtrash.c @@ -0,0 +1,806 @@ +/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ + +#include <config.h> + +#include <sys/types.h> +#include <sys/stat.h> +#include <errno.h> +#include <unistd.h> +#include <fcntl.h> +#include <string.h> + +#include <glib/gstdio.h> +#include <glib/gi18n.h> +#include <gio/gioerror.h> +#include <gio/gfile.h> +#include <gio/gvolumemonitor.h> +#include <gio/gthemedicon.h> + +#include "gvfsuriutils.h" + +#include "gvfsbackendtrash.h" +#include "gvfsjobopenforread.h" +#include "gvfsjobread.h" +#include "gvfsjobseekread.h" +#include "gvfsjobopenforwrite.h" +#include "gvfsjobwrite.h" +#include "gvfsjobclosewrite.h" +#include "gvfsjobseekwrite.h" +#include "gvfsjobsetdisplayname.h" +#include "gvfsjobqueryinfo.h" +#include "gvfsjobdelete.h" +#include "gvfsjobqueryfsinfo.h" +#include "gvfsjobqueryattributes.h" +#include "gvfsjobenumerate.h" +#include "gvfsdaemonprotocol.h" + + +struct _GVfsBackendTrash +{ + GVfsBackend parent_instance; + +}; + + +G_DEFINE_TYPE (GVfsBackendTrash, g_vfs_backend_trash, G_VFS_TYPE_BACKEND); + +static char * +escape_pathname (const char *dir) +{ + const char *p; + char *d, *res; + int count; + char c; + + count = 0; + p = dir; + while (*p) + { + if (*p == '%' || + *p == '/') + count++; + p++; + } + + res = g_malloc (strlen (dir) + count + 1); + + p = dir; + d = res; + while (*p) + { + c = *p++; + if (c == '%') + { + *d++ = '%'; + *d++ = '%'; + } + else if (c == '/') + { + *d++ = '%'; + *d++ = 's'; + } + else + *d++ = c; + } + *d = 0; + + return res; +} + +static char * +unescape_pathname (const char *escaped_dir, int len) +{ + char *dir, *d; + const char *p, *end; + char c; + + if (len == -1) + len = strlen (escaped_dir); + + dir = g_malloc (len + 1); + + p = escaped_dir; + d = dir; + end = p + len; + while (p < end) + { + c = *p++; + if (c == '%') + { + if (p == end) + *d++ = '%'; + else + { + c = *p++; + if (c == 's') + *d++ = '/'; + else + *d++ = '%'; + } + } + else + *d++ = c; + } + *d = 0; + + return dir; +} + +static char * +get_top_dir_for_trash_dir (const char *trash_dir) +{ + char *basename, *dirname; + char *user_trash_basename; + char *user_sys_dir, *res; + + basename = g_path_get_basename (trash_dir); + if (strcmp (basename, "Trash") == 0) + { + /* This is $XDG_DATA_DIR/Trash */ + g_free (basename); + return g_path_get_dirname (trash_dir); + } + + user_trash_basename = g_strdup_printf (".Trash-%d", getuid()); + if (strcmp (basename, user_trash_basename) == 0) + { + g_free (user_trash_basename); + g_free (basename); + return g_path_get_dirname (trash_dir); + } + g_free (user_trash_basename); + + user_sys_dir = g_strdup_printf ("%d", getuid()); + if (strcmp (basename, user_sys_dir) == 0) + { + g_free (user_sys_dir); + dirname = g_path_get_dirname (trash_dir); + g_free (basename); + basename = g_path_get_basename (dirname); + + if (strcmp (basename, ".Trash") == 0) + { + res = g_path_get_dirname (dirname); + g_free (dirname); + g_free (basename); + return res; + } + + g_free (dirname); + } + g_free (user_sys_dir); + g_free (basename); + + /* Weird, but we return something at least */ + return g_strdup (trash_dir); +} + +/* FALSE => root */ +static gboolean +decode_path (const char *filename, char **trashdir, char **trashfile, char **relative_path, char **topdir) +{ + const char *first_entry, *first_entry_end; + char *first_item; + + if (*filename == 0 || *filename != '/') + return FALSE; + + while (*filename == '/') + filename++; + + if (*filename == 0) + return FALSE; + + first_entry = filename; + + while (*filename != 0 && *filename != '/') + filename++; + + first_entry_end = filename; + + while (*filename == '/') + filename++; + + first_item = unescape_pathname (first_entry, first_entry_end - first_entry); + *trashfile = g_path_get_basename (first_item); + *trashdir = g_path_get_dirname (first_item); + *topdir = get_top_dir_for_trash_dir (*trashdir); + + if (*filename) + *relative_path = g_strdup (filename); + else + *relative_path = NULL; + return TRUE; +} + +static void +g_vfs_backend_trash_finalize (GObject *object) +{ + GVfsBackendTrash *backend; + + backend = G_VFS_BACKEND_TRASH (object); + + if (G_OBJECT_CLASS (g_vfs_backend_trash_parent_class)->finalize) + (*G_OBJECT_CLASS (g_vfs_backend_trash_parent_class)->finalize) (object); +} + +static void +g_vfs_backend_trash_init (GVfsBackendTrash *trash_backend) +{ + GVfsBackend *backend = G_VFS_BACKEND (trash_backend); + GMountSpec *mount_spec; + + g_vfs_backend_set_display_name (backend, "trash"); + + mount_spec = g_mount_spec_new ("trash"); + g_vfs_backend_set_mount_spec (backend, mount_spec); + g_mount_spec_unref (mount_spec); + +} + +static gboolean +try_mount (GVfsBackend *backend, + GVfsJobMount *job, + GMountSpec *mount_spec, + GMountSource *mount_source, + gboolean is_automount) +{ + g_vfs_job_succeeded (G_VFS_JOB (job)); + return TRUE; + } + +static void +do_open_for_read (GVfsBackend *backend, + GVfsJobOpenForRead *job, + const char *filename) +{ + char *trashdir, *topdir, *relative_path, *trashfile; + + if (!decode_path (filename, &trashdir, &trashfile, &relative_path, &topdir)) + g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, + G_IO_ERROR_IS_DIRECTORY, + _("Can't open directory")); + else + { + GFile *file; + char *dir; + GError *error; + GFileInputStream *stream; + + dir = g_build_filename (trashdir, "files", trashfile, relative_path, NULL); + file = g_file_new_for_path (dir); + + error = NULL; + stream = g_file_read (file, + G_VFS_JOB (job)->cancellable, + &error); + g_object_unref (file); + + if (stream) + { + g_vfs_job_open_for_read_set_handle (job, stream); + g_vfs_job_open_for_read_set_can_seek (job, + g_file_input_stream_can_seek (G_FILE_INPUT_STREAM (stream))); + g_vfs_job_succeeded (G_VFS_JOB (job)); + } + else + { + g_vfs_job_failed_from_error (G_VFS_JOB (job), error); + g_error_free (error); + } + + g_free (trashdir); + g_free (trashfile); + g_free (relative_path); + g_free (topdir); + } +} + + +static void +do_read (GVfsBackend *backend, + GVfsJobRead *job, + GVfsBackendHandle _handle, + char *buffer, + gsize bytes_requested) +{ + GInputStream *stream; + gssize res; + GError *error; + + stream = G_INPUT_STREAM (_handle); + + error = NULL; + res = g_input_stream_read (stream, + buffer, bytes_requested, + G_VFS_JOB (job)->cancellable, + &error); + + if (res != -1) + { + g_vfs_job_read_set_size (job, res); + g_vfs_job_succeeded (G_VFS_JOB (job)); + } + else + { + g_vfs_job_failed_from_error (G_VFS_JOB (job), error); + g_error_free (error); + } +} + +static void +do_seek_on_read (GVfsBackend *backend, + GVfsJobSeekRead *job, + GVfsBackendHandle _handle, + goffset offset, + GSeekType type) +{ + GFileInputStream *stream; + GError *error; + + stream = G_FILE_INPUT_STREAM (_handle); + + error = NULL; + if (g_file_input_stream_seek (stream, + offset, type, + G_VFS_JOB (job)->cancellable, + &error)) + { + g_vfs_job_seek_read_set_offset (job, + g_file_input_stream_tell (stream)); + g_vfs_job_succeeded (G_VFS_JOB (job)); + } + else + { + g_vfs_job_failed_from_error (G_VFS_JOB (job), error); + g_error_free (error); + } +} + +static void +do_close_read (GVfsBackend *backend, + GVfsJobCloseRead *job, + GVfsBackendHandle _handle) +{ + GInputStream *stream; + GError *error; + + stream = G_INPUT_STREAM (_handle); + + error = NULL; + if (g_input_stream_close (stream, + G_VFS_JOB (job)->cancellable, + &error)) + g_vfs_job_succeeded (G_VFS_JOB (job)); + else + { + g_vfs_job_failed_from_error (G_VFS_JOB (job), error); + g_error_free (error); + } + +} + +static void +add_extra_trash_info (GFileInfo *file_info, + const char *topdir, + const char *info_dir, + const char *filename, + const char *relative_path) +{ + char *info_filename; + char *info_path; + char *orig_path, *orig_path_key, *orig_path_unescaped, *date; + GKeyFile *keyfile; + char *display_name; + + + /* Override all writability */ + g_file_info_set_attribute_boolean (file_info, + G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE, + FALSE); + g_file_info_set_attribute_boolean (file_info, + G_FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE, + FALSE); + g_file_info_set_attribute_boolean (file_info, + G_FILE_ATTRIBUTE_ACCESS_CAN_RENAME, + FALSE); + + /* But we can delete */ + g_file_info_set_attribute_boolean (file_info, + G_FILE_ATTRIBUTE_ACCESS_CAN_DELETE, + TRUE); + + info_filename = g_strconcat (filename, ".trashinfo", NULL); + info_path = g_build_filename (info_dir, info_filename, NULL); + g_free (info_filename); + + keyfile = g_key_file_new (); + if (g_key_file_load_from_file (keyfile, info_path, G_KEY_FILE_NONE, NULL)) + { + orig_path_key = g_key_file_get_string (keyfile, "Trash Info", "Path", NULL); + if (orig_path_key) + { + orig_path_unescaped = g_uri_unescape_string (orig_path_key, NULL, ""); + + if (orig_path_unescaped) + { + /* Set display name and edit name based of original basename */ + display_name = g_filename_display_basename (orig_path_unescaped); + + g_file_info_set_edit_name (file_info, display_name); + + if (strstr (display_name, "\357\277\275") != NULL) + { + char *p = display_name; + display_name = g_strconcat (display_name, _(" (invalid encoding)"), NULL); + g_free (p); + } + g_file_info_set_display_name (file_info, display_name); + g_free (display_name); + + + /* Set orig_path */ + + if (g_path_is_absolute (orig_path_unescaped)) + orig_path = g_build_filename (orig_path_unescaped, relative_path, NULL); + else + orig_path = g_build_filename (topdir, orig_path_unescaped, relative_path, NULL); + + + g_file_info_set_attribute_byte_string (file_info, + "trash:orig_path", + orig_path); + g_free (orig_path); + g_free (orig_path_unescaped); + } + + + g_free (orig_path_key); + } + + date = g_key_file_get_string (keyfile, "Trash Info", "DeletionDate", NULL); + if (date && g_utf8_validate (date, -1, NULL)) + g_file_info_set_attribute_string (file_info, + "trash:deletion_date", + date); + g_free (date); + } + g_key_file_free (keyfile); + g_free (info_path); +} + +static void +enumerate_root_trashdir (GVfsBackend *backend, + GVfsJobEnumerate *job, + const char *topdir, + const char *trashdir) +{ + GFile *file, *files_file; + GFileEnumerator *enumerator; + GFileInfo *info; + const char *name; + char *new_name, *new_name_escaped; + char *info_dir; + + info_dir = g_build_filename (trashdir, "info", NULL); + + file = g_file_new_for_path (trashdir); + files_file = g_file_get_child (file, "files"); + enumerator = + g_file_enumerate_children (files_file, + job->attributes, + job->flags, + G_VFS_JOB (job)->cancellable, + NULL); + g_object_unref (files_file); + g_object_unref (file); + + if (enumerator) + { + while ((info = g_file_enumerator_next_file (enumerator, + G_VFS_JOB (job)->cancellable, + NULL)) != NULL) + { + name = g_file_info_get_name (info); + + /* Get the display name, etc */ + add_extra_trash_info (info, + topdir, + info_dir, + name, + NULL); + + + /* Update the name to also have the trash dir */ + new_name = g_build_filename (trashdir, name, NULL); + new_name_escaped = escape_pathname (new_name); + g_free (new_name); + g_file_info_set_name (info, new_name_escaped); + g_free (new_name_escaped); + + g_vfs_job_enumerate_add_info (job, info); + g_object_unref (info); + } + + g_file_enumerator_close (enumerator, + G_VFS_JOB (job)->cancellable, + NULL); + g_object_unref (enumerator); + } +} + + +static void +enumerate_root_topdir (GVfsBackend *backend, + GVfsJobEnumerate *job, + const char *topdir) +{ + struct stat statbuf; + char *sysadmin_dir, *sysadmin_dir_uid; + char *user_trash_basename, *user_trash; + + /* TODO: This stats all filesystems. It should take more care to not get locked up + on e.g. hanged nfs mounts. See gnome-vfs-unix-mounts.c for some code to handle this. */ + + sysadmin_dir = g_build_filename (topdir, ".Trash", NULL); + if (lstat (sysadmin_dir, &statbuf) == 0 && + S_ISDIR (statbuf.st_mode) && + statbuf.st_mode & S_ISVTX) + { + /* We have a valid sysadmin .Trash dir, look for uid subdir */ + sysadmin_dir_uid = g_strdup_printf ("%s/%d", sysadmin_dir, getuid()); + + if (lstat (sysadmin_dir_uid, &statbuf) == 0 && + S_ISDIR (statbuf.st_mode) && + statbuf.st_uid == getuid()) + enumerate_root_trashdir (backend, job, topdir, sysadmin_dir_uid); + + g_free (sysadmin_dir_uid); + } + g_free (sysadmin_dir); + + user_trash_basename = g_strdup_printf (".Trash-%d", getuid()); + user_trash = g_build_filename (topdir, user_trash_basename, NULL); + g_free (user_trash_basename); + + if (lstat (user_trash, &statbuf) == 0 && + S_ISDIR (statbuf.st_mode) && + statbuf.st_uid == getuid()) + enumerate_root_trashdir (backend, job, topdir, user_trash); + + g_free (user_trash); +} + +static void +enumerate_root (GVfsBackend *backend, + GVfsJobEnumerate *job) +{ + GVolumeMonitor *monitor; + GList *volumes, *l; + GVolume *volume; + GFile *topdir_file; + char *topdir; + char *home_trash; + + /* Always succeeds */ + g_vfs_job_succeeded (G_VFS_JOB (job)); + + home_trash = g_build_filename (g_get_user_data_dir (), "Trash", NULL); + enumerate_root_trashdir (backend, job, g_get_user_data_dir (), home_trash); + g_free (home_trash); + + monitor = g_volume_monitor_get (); + + + volumes = g_volume_monitor_get_mounted_volumes (monitor); + + for (l = volumes; l != NULL; l = l->next) + { + volume = l->data; + + topdir_file = g_volume_get_root (volume); + if (topdir_file) + { + if (g_file_is_native (topdir_file)) + { + topdir = g_file_get_path (topdir_file); + + if (topdir) + enumerate_root_topdir (backend, job, topdir); + + g_free (topdir); + } + + g_object_unref (topdir_file); + } + + g_object_unref (volume); + } + g_list_free (volumes); + + + g_vfs_job_enumerate_done (job); +} + +static void +do_enumerate (GVfsBackend *backend, + GVfsJobEnumerate *job, + const char *filename, + GFileAttributeMatcher *attribute_matcher, + GFileQueryInfoFlags flags) +{ + char *trashdir, *topdir, *relative_path, *trashfile; + + if (!decode_path (filename, &trashdir, &trashfile, &relative_path, &topdir)) + enumerate_root (backend, job); + else + { + GFile *file; + GFileEnumerator *enumerator; + GFileInfo *info; + const char *name; + char *dir; + GError *error; + + dir = g_build_filename (trashdir, "files", trashfile, relative_path, NULL); + file = g_file_new_for_path (dir); + error = NULL; + enumerator = + g_file_enumerate_children (file, + job->attributes, + job->flags, + G_VFS_JOB (job)->cancellable, + &error); + g_free (dir); + g_object_unref (file); + + if (enumerator) + { + g_vfs_job_succeeded (G_VFS_JOB (job)); + + while ((info = g_file_enumerator_next_file (enumerator, + G_VFS_JOB (job)->cancellable, + NULL)) != NULL) + { + name = g_file_info_get_name (info); + + g_vfs_job_enumerate_add_info (job, info); + g_object_unref (info); + } + + g_file_enumerator_close (enumerator, + G_VFS_JOB (job)->cancellable, + NULL); + g_object_unref (enumerator); + + g_vfs_job_enumerate_done (job); + } + else + { + g_vfs_job_failed_from_error (G_VFS_JOB (job), error); + g_error_free (error); + } + + g_free (trashdir); + g_free (trashfile); + g_free (relative_path); + g_free (topdir); + } +} + +static void +do_query_info (GVfsBackend *backend, + GVfsJobQueryInfo *job, + const char *filename, + GFileQueryInfoFlags flags, + GFileInfo *info, + GFileAttributeMatcher *matcher) +{ + char *trashdir, *topdir, *relative_path, *trashfile; + GIcon *icon; + + if (!decode_path (filename, &trashdir, &trashfile, &relative_path, &topdir)) + { + /* The trash:/// root */ + g_file_info_set_file_type (info, G_FILE_TYPE_DIRECTORY); + g_file_info_set_display_name (info, _("Trashcan")); + g_file_info_set_content_type (info, "inode/directory"); + + /* TODO: Add -full version? */ + icon = g_themed_icon_new ("user-trash"); + /*TODO: Crashes: g_file_info_set_icon (info, icon); */ + g_object_unref (icon); + + g_file_info_set_attribute_boolean (info, + G_FILE_ATTRIBUTE_STD_IS_VIRTUAL, + TRUE); + g_file_info_set_attribute_boolean (info, + G_FILE_ATTRIBUTE_ACCESS_CAN_READ, + TRUE); + g_file_info_set_attribute_boolean (info, + G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE, + FALSE); + g_file_info_set_attribute_boolean (info, + G_FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE, + FALSE); + g_file_info_set_attribute_boolean (info, + G_FILE_ATTRIBUTE_ACCESS_CAN_DELETE, + FALSE); + g_file_info_set_attribute_boolean (info, + G_FILE_ATTRIBUTE_ACCESS_CAN_RENAME, + FALSE); + + g_vfs_job_succeeded (G_VFS_JOB (job)); + } + else + { + GFile *file; + GFileInfo *local_info; + char *path; + GError *error; + char *info_dir; + + path = g_build_filename (trashdir, "files", trashfile, relative_path, NULL); + file = g_file_new_for_path (path); + g_free (path); + + error = NULL; + local_info = g_file_query_info (file, + job->attributes, + job->flags, + G_VFS_JOB (job)->cancellable, + &error); + g_object_unref (file); + + if (local_info) + { + g_file_info_copy_into (local_info, info); + + info_dir = g_build_filename (trashdir, "info", NULL); + add_extra_trash_info (info, + topdir, + info_dir, + trashfile, + relative_path); + g_free (info_dir); + + g_object_unref (local_info); + g_vfs_job_succeeded (G_VFS_JOB (job)); + } + else + { + g_vfs_job_failed_from_error (G_VFS_JOB (job), error); + g_error_free (error); + } + + g_free (trashdir); + g_free (trashfile); + g_free (relative_path); + g_free (topdir); + } +} + +static void +do_delete (GVfsBackend *backend, + GVfsJobDelete *job, + const char *filename) +{ +} + +static void +g_vfs_backend_trash_class_init (GVfsBackendTrashClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + GVfsBackendClass *backend_class = G_VFS_BACKEND_CLASS (klass); + + gobject_class->finalize = g_vfs_backend_trash_finalize; + + backend_class->try_mount = try_mount; + backend_class->open_for_read = do_open_for_read; + backend_class->read = do_read; + backend_class->seek_on_read = do_seek_on_read; + backend_class->close_read = do_close_read; + backend_class->query_info = do_query_info; + backend_class->enumerate = do_enumerate; + backend_class->delete = do_delete; +} |