/* GIO - GLib Input, Output and Streaming Library * * Copyright (C) 2014 Ross Lagerwall * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General * Public License along with this library; if not, see * . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "gvfsbackendnfs.h" #include "gvfsjobopenforread.h" #include "gvfsjobread.h" #include "gvfsjobseekread.h" #include "gvfsjobdelete.h" #include "gvfsjobopenforwrite.h" #include "gvfsjobwrite.h" #include "gvfsjobclosewrite.h" #include "gvfsjobseekwrite.h" #include "gvfsjobtruncate.h" #include "gvfsjobsetdisplayname.h" #include "gvfsjobqueryinfo.h" #include "gvfsjobqueryinforead.h" #include "gvfsjobqueryinfowrite.h" #include "gvfsjobqueryfsinfo.h" #include "gvfsjobqueryattributes.h" #include "gvfsjobsetattribute.h" #include "gvfsjobenumerate.h" #include "gvfsjobmove.h" #include "gvfsdaemonprotocol.h" #include "gvfsdaemonutils.h" #include "gvfsutils.h" #include #include #include struct _GVfsBackendNfs { GVfsBackend parent_instance; struct nfs_context *ctx; GSource *source; mode_t umask; /* cached umask of process */ }; typedef struct { GSource source; struct nfs_context *ctx; GVfsBackendNfs *backend; int fd; /* fd registered for IO */ gpointer tag; /* tag for fd attached to this source */ int events; /* IO events we're interested in */ } NfsSource; G_DEFINE_TYPE (GVfsBackendNfs, g_vfs_backend_nfs, G_VFS_TYPE_BACKEND) static void g_vfs_backend_nfs_init (GVfsBackendNfs *backend) { } static void g_vfs_backend_nfs_destroy_context (GVfsBackendNfs *backend) { if (backend->ctx) { nfs_destroy_context (backend->ctx); backend->ctx = NULL; } if (backend->source) { g_source_destroy (backend->source); g_source_unref (backend->source); backend->source = NULL; } } static void g_vfs_backend_nfs_finalize (GObject *object) { GVfsBackendNfs *backend = G_VFS_BACKEND_NFS (object); g_vfs_backend_nfs_destroy_context (backend); if (G_OBJECT_CLASS (g_vfs_backend_nfs_parent_class)->finalize) (*G_OBJECT_CLASS (g_vfs_backend_nfs_parent_class)->finalize) (object); } static gboolean nfs_source_prepare (GSource *source, gint *timeout) { NfsSource *nfs_source = (NfsSource *) source; int events, fd; *timeout = -1; fd = nfs_get_fd (nfs_source->ctx); events = nfs_which_events (nfs_source->ctx); if (fd < 0) { g_vfs_backend_force_unmount (G_VFS_BACKEND (nfs_source->backend)); g_vfs_backend_nfs_destroy_context (nfs_source->backend); } else if (fd != nfs_source->fd) { g_source_remove_unix_fd (source, nfs_source->tag); nfs_source->fd = fd; nfs_source->events = events; nfs_source->tag = g_source_add_unix_fd (source, nfs_source->fd, events); } else if (events != nfs_source->events) { nfs_source->events = events; g_source_modify_unix_fd (source, nfs_source->tag, events); } return FALSE; } static gboolean nfs_source_dispatch (GSource *source, GSourceFunc callback, gpointer user_data) { NfsSource *nfs_source = (NfsSource *) source; int err; err = nfs_service (nfs_source->ctx, g_source_query_unix_fd (source, nfs_source->tag)); if (err) { g_warning ("nfs_service error: %d, %s\n", err, nfs_get_error (nfs_source->ctx)); g_vfs_backend_force_unmount (G_VFS_BACKEND (nfs_source->backend)); g_vfs_backend_nfs_destroy_context (nfs_source->backend); } return G_SOURCE_CONTINUE; } static void do_mount (GVfsBackend *backend, GVfsJobMount *job, GMountSpec *mount_spec, GMountSource *mount_source, gboolean is_automount) { GVfsBackendNfs *op_backend = G_VFS_BACKEND_NFS (backend); GMountSpec *nfs_mount_spec; GSource *source; NfsSource *nfs_source; struct exportnode *export_list, *ptr; const char *host, *debug; char *basename, *display_name, *export = NULL; int err, debug_val; size_t pathlen = strlen (mount_spec->mount_prefix); size_t exportlen = SIZE_MAX; static GSourceFuncs nfs_source_callbacks = { nfs_source_prepare, NULL, nfs_source_dispatch, NULL }; host = g_mount_spec_get (mount_spec, "host"); if (!host) { g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, _("No hostname specified")); return; } export_list = mount_getexports (host); /* Find the shortest matching mount. E.g. if the given mount_prefix is * /some/long/path and there exist two mounts, /some and /some/long, match * against /some. */ for (ptr = export_list; ptr; ptr = ptr->ex_next) { /* First check that the NFS mount point is a prefix of the mount_prefix. */ if (g_str_has_prefix (mount_spec->mount_prefix, ptr->ex_dir)) { size_t this_exportlen = strlen (ptr->ex_dir); /* Check if the mount_prefix is longer than the NFS mount point. * E.g. mount_prefix: /mnt/file, mount point: /mnt */ if (pathlen > this_exportlen) { /* Check if the mount_prefix has a slash at the correct point. * E.g. if the mount point is /mnt, then it's a match if the * mount_prefix is /mnt/a but not a match if the mount_prefix is * /mnta. Choose it if it is the shortest found so far. */ char *s = mount_spec->mount_prefix + this_exportlen; if (*s == '/' && this_exportlen < exportlen) { export = ptr->ex_dir; exportlen = this_exportlen; } } /* The mount_prefix and NFS mount point are identical. Choose it if * it is the shortest found so far. */ else if (this_exportlen < exportlen) { export = ptr->ex_dir; exportlen = this_exportlen; } } } if (!export) { mount_free_export_list (export_list); g_vfs_job_failed_literal (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_NOT_FOUND, _("Mount point does not exist")); return; } export = strdup (export); mount_free_export_list (export_list); op_backend->ctx = nfs_init_context (); debug = g_getenv ("GVFS_NFS_DEBUG"); if (debug) debug_val = atoi (debug); else debug_val = 0; nfs_set_debug (op_backend->ctx, debug_val); err = nfs_mount (op_backend->ctx, host, export); if (err) { if (err == -EACCES) { g_vfs_job_failed_literal (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED, _("Permission denied: Perhaps this host is disallowed or a privileged port is needed")); } else { g_vfs_job_failed_from_errno (G_VFS_JOB (job), -err); } g_free (export); return; } source = g_source_new (&nfs_source_callbacks, sizeof (NfsSource)); nfs_source = (NfsSource *) source; nfs_source->ctx = op_backend->ctx; nfs_source->backend = op_backend; nfs_source->events = nfs_which_events (op_backend->ctx); nfs_source->fd = nfs_get_fd (op_backend->ctx); nfs_source->tag = g_source_add_unix_fd (source, nfs_source->fd, nfs_source->events); g_source_attach (source, NULL); op_backend->source = source; basename = g_path_get_basename (export); /* Translators: This is " on " and is used as name for an NFS mount */ display_name = g_strdup_printf (_("%s on %s"), basename, host); g_vfs_backend_set_display_name (backend, display_name); g_free (basename); g_free (display_name); g_vfs_backend_set_icon_name (G_VFS_BACKEND (backend), "folder-remote"); g_vfs_backend_set_symbolic_icon_name (G_VFS_BACKEND (backend), "folder-remote-symbolic"); nfs_mount_spec = g_mount_spec_new ("nfs"); g_mount_spec_set (nfs_mount_spec, "host", host); g_mount_spec_set_mount_prefix (nfs_mount_spec, export); g_vfs_backend_set_mount_spec (backend, nfs_mount_spec); g_mount_spec_unref (nfs_mount_spec); g_free (export); /* cache the process's umask for later */ op_backend->umask = umask (0); umask (op_backend->umask); g_vfs_job_succeeded (G_VFS_JOB (job)); } static void null_cb (int err, struct nfs_context *ctx, void *data, void *private_data) { } static void generic_cb (int err, struct nfs_context *ctx, void *data, void *private_data) { GVfsJob *job = G_VFS_JOB (private_data); if (err == 0) g_vfs_job_succeeded (job); else g_vfs_job_failed_from_errno (job, -err); } static char * create_etag (uint64_t mtime, uint32_t nsec) { return g_strdup_printf ("%lu:%lu", (long unsigned int)mtime, (long unsigned int)nsec); } static void open_for_read_fstat_cb (int err, struct nfs_context *ctx, void *data, void *private_data) { GVfsJob *job = G_VFS_JOB (private_data); if (err == 0) { GVfsJobOpenForRead *op_job = G_VFS_JOB_OPEN_FOR_READ (job); struct stat *st = data; if (S_ISDIR (st->st_mode)) { struct nfsfh *fh = op_job->backend_handle; nfs_close_async (ctx, fh, null_cb, NULL); g_vfs_job_failed_literal (job, G_IO_ERROR, G_IO_ERROR_IS_DIRECTORY, _("Can’t open directory")); return; } } g_vfs_job_succeeded (job); } static void open_for_read_cb (int err, struct nfs_context *ctx, void *data, void *private_data) { if (err == 0) { GVfsJobOpenForRead *op_job = G_VFS_JOB_OPEN_FOR_READ (private_data); g_vfs_job_open_for_read_set_handle (op_job, data); g_vfs_job_open_for_read_set_can_seek (op_job, TRUE); nfs_fstat_async (ctx, data, open_for_read_fstat_cb, private_data); } else { g_vfs_job_failed_from_errno (G_VFS_JOB (private_data), -err); } } static gboolean try_open_for_read (GVfsBackend *backend, GVfsJobOpenForRead *job, const char *filename) { GVfsBackendNfs *op_backend = G_VFS_BACKEND_NFS (backend); nfs_open_async (op_backend->ctx, filename, O_RDONLY, open_for_read_cb, job); return TRUE; } static void read_cb (int err, struct nfs_context *ctx, void *data, void *private_data) { GVfsJob *job = G_VFS_JOB (private_data); if (err >= 0) { GVfsJobRead *op_job = G_VFS_JOB_READ (job); memcpy (op_job->buffer, data, err); g_vfs_job_read_set_size (op_job, err); g_vfs_job_succeeded (job); } else { g_vfs_job_failed_from_errno (job, -err); } } static gboolean try_read (GVfsBackend *backend, GVfsJobRead *job, GVfsBackendHandle _handle, char *buffer, gsize bytes_requested) { GVfsBackendNfs *op_backend = G_VFS_BACKEND_NFS (backend); struct nfsfh *fh = _handle; nfs_read_async (op_backend->ctx, fh, bytes_requested, read_cb, job); return TRUE; } static const char * set_type_from_mode (GFileInfo *info, uint64_t mode) { GFileType type = G_FILE_TYPE_UNKNOWN; const char *mimetype = NULL; if (S_ISREG (mode)) type = G_FILE_TYPE_REGULAR; else if (S_ISDIR (mode)) { type = G_FILE_TYPE_DIRECTORY; mimetype = "inode/directory"; } else if (S_ISFIFO (mode)) { type = G_FILE_TYPE_SPECIAL; mimetype = "inode/fifo"; } else if (S_ISSOCK (mode)) { type = G_FILE_TYPE_SPECIAL; mimetype = "inode/socket"; } else if (S_ISCHR (mode)) { type = G_FILE_TYPE_SPECIAL; mimetype = "inode/chardevice"; } else if (S_ISBLK (mode)) { type = G_FILE_TYPE_SPECIAL; mimetype = "inode/blockdevice"; } else if (S_ISLNK (mode)) { type = G_FILE_TYPE_SYMBOLIC_LINK; g_file_info_set_is_symlink (info, TRUE); mimetype = "inode/symlink"; } g_file_info_set_file_type (info, type); return mimetype; } static void set_name_info (GFileInfo *info, const char *mimetype, const char *basename, GFileAttributeMatcher *matcher) { char *free_mimetype = NULL; gboolean uncertain_content_type = FALSE; g_file_info_set_name (info, basename); if (basename[0] == '.') g_file_info_set_is_hidden (info, TRUE); if (basename[strlen (basename) -1] == '~') g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_STANDARD_IS_BACKUP, TRUE); if (g_file_attribute_matcher_matches (matcher, G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME) || g_file_attribute_matcher_matches (matcher, G_FILE_ATTRIBUTE_STANDARD_EDIT_NAME)) { char *edit_name = gvfs_file_info_populate_names_as_local (info, basename); g_free (edit_name); } if (mimetype == NULL) { if (basename) { free_mimetype = g_content_type_guess (basename, NULL, 0, &uncertain_content_type); mimetype = free_mimetype; } else mimetype = "application/octet-stream"; } if (!uncertain_content_type) g_file_info_set_content_type (info, mimetype); g_file_info_set_attribute_string (info, G_FILE_ATTRIBUTE_STANDARD_FAST_CONTENT_TYPE, mimetype); if (g_file_attribute_matcher_matches (matcher, G_FILE_ATTRIBUTE_STANDARD_ICON) || g_file_attribute_matcher_matches (matcher, G_FILE_ATTRIBUTE_STANDARD_SYMBOLIC_ICON)) { GIcon *icon = NULL; GIcon *symbolic_icon = NULL; icon = g_content_type_get_icon (mimetype); symbolic_icon = g_content_type_get_symbolic_icon (mimetype); if (icon == NULL) icon = g_themed_icon_new ("text-x-generic"); if (symbolic_icon == NULL) symbolic_icon = g_themed_icon_new ("text-x-generic-symbolic"); g_file_info_set_icon (info, icon); g_file_info_set_symbolic_icon (info, symbolic_icon); g_object_unref (icon); g_object_unref (symbolic_icon); } if (free_mimetype) g_free (free_mimetype); } static void set_info_from_stat (GFileInfo *info, struct nfs_stat_64 *st, GFileAttributeMatcher *matcher) { g_file_info_set_size (info, st->nfs_size); g_file_info_set_attribute_uint64 (info, G_FILE_ATTRIBUTE_STANDARD_ALLOCATED_SIZE, st->nfs_used); g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_MODE, st->nfs_mode); g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_UID, st->nfs_uid); g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_GID, st->nfs_gid); g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_NLINK, st->nfs_nlink); g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_DEVICE, st->nfs_dev); g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_RDEV, st->nfs_rdev); g_file_info_set_attribute_uint64 (info, G_FILE_ATTRIBUTE_UNIX_INODE, st->nfs_ino); g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_BLOCK_SIZE, st->nfs_blksize); g_file_info_set_attribute_uint64 (info, G_FILE_ATTRIBUTE_UNIX_BLOCKS, st->nfs_blocks); g_file_info_set_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_ACCESS, st->nfs_atime); g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_TIME_ACCESS_USEC, st->nfs_atime_nsec / 1000); g_file_info_set_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_MODIFIED, st->nfs_mtime); g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_TIME_MODIFIED_USEC, st->nfs_mtime_nsec / 1000); g_file_info_set_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_CHANGED, st->nfs_ctime); g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_TIME_CHANGED_USEC, st->nfs_ctime_nsec / 1000); g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_TRASH, FALSE); } static void query_info_on_read_cb (int err, struct nfs_context *ctx, void *data, void *private_data) { GVfsJob *job = G_VFS_JOB (private_data); if (err == 0) { GVfsJobQueryInfoRead *op_job = G_VFS_JOB_QUERY_INFO_READ (job); struct nfs_stat_64 *st = data; set_info_from_stat (op_job->file_info, st, op_job->attribute_matcher); set_type_from_mode (op_job->file_info, st->nfs_mode); g_vfs_job_succeeded (job); } else { g_vfs_job_failed_from_errno (job, -err); } } static gboolean try_query_info_on_read (GVfsBackend *backend, GVfsJobQueryInfoRead *job, GVfsBackendHandle _handle, GFileInfo *info, GFileAttributeMatcher *attribute_matcher) { GVfsBackendNfs *op_backend = G_VFS_BACKEND_NFS (backend); struct nfsfh *nfsfh = _handle; nfs_fstat64_async (op_backend->ctx, nfsfh, query_info_on_read_cb, job); return TRUE; } static void seek_on_read_cb (int err, struct nfs_context *ctx, void *data, void *private_data) { GVfsJob *job = G_VFS_JOB (private_data); if (err >= 0) { GVfsJobSeekRead *op_job = G_VFS_JOB_SEEK_READ (job); uint64_t *pos = data; g_vfs_job_seek_read_set_offset (op_job, *pos); g_vfs_job_succeeded (job); } else { g_vfs_job_failed_from_errno (job, -err); } } static gboolean try_seek_on_read (GVfsBackend *backend, GVfsJobSeekRead *job, GVfsBackendHandle _handle, goffset offset, GSeekType type) { GVfsBackendNfs *op_backend = G_VFS_BACKEND_NFS (backend); struct nfsfh *fh = _handle; nfs_lseek_async (op_backend->ctx, fh, offset, gvfs_seek_type_to_lseek (type), seek_on_read_cb, job); return TRUE; } static gboolean try_close_read (GVfsBackend *backend, GVfsJobCloseRead *job, GVfsBackendHandle _handle) { GVfsBackendNfs *op_backend = G_VFS_BACKEND_NFS (backend); struct nfsfh *fh = _handle; nfs_close_async (op_backend->ctx, fh, generic_cb, job); return TRUE; } static gboolean try_make_directory (GVfsBackend *backend, GVfsJobMakeDirectory *job, const char *filename) { GVfsBackendNfs *op_backend = G_VFS_BACKEND_NFS (backend); nfs_mkdir_async (op_backend->ctx, filename, generic_cb, job); return TRUE; } static void unlink_cb (int err, struct nfs_context *ctx, void *data, void *private_data) { GVfsJob *job = G_VFS_JOB (private_data); GVfsJobDelete *op_job = G_VFS_JOB_DELETE (job); if (err == 0) g_vfs_job_succeeded (job); else if (err == -EPERM || err == -EISDIR) nfs_rmdir_async (ctx, op_job->filename, generic_cb, private_data); else g_vfs_job_failed_from_errno (job, -err); } static gboolean try_delete (GVfsBackend *backend, GVfsJobDelete *job, const char *filename) { GVfsBackendNfs *op_backend = G_VFS_BACKEND_NFS (backend); nfs_unlink_async (op_backend->ctx, filename, unlink_cb, job); return TRUE; } static gboolean try_make_symlink (GVfsBackend *backend, GVfsJobMakeSymlink *job, const char *filename, const char *symlink_value) { GVfsBackendNfs *op_backend = G_VFS_BACKEND_NFS (backend); nfs_symlink_async (op_backend->ctx, symlink_value, filename, generic_cb, job); return TRUE; } typedef struct { struct nfsfh *fh; GVfsJob *job; char *filename; char *tempname; char *backup_filename; uint64_t uid; uint64_t gid; uint64_t nlink; uint64_t mode; gboolean is_symlink; } WriteHandle; static void write_handle_free (WriteHandle *handle) { if (handle->job) g_object_unref (handle->job); if (handle->filename) g_free (handle->filename); if (handle->tempname) g_free (handle->tempname); if (handle->backup_filename) g_free (handle->backup_filename); g_slice_free (WriteHandle, handle); } static void append_cb (int err, struct nfs_context *ctx, void *data, void *private_data) { GVfsJob *job = G_VFS_JOB (private_data); if (err == 0) { GVfsJobOpenForWrite *op_job = G_VFS_JOB_OPEN_FOR_WRITE (job); WriteHandle *handle = g_slice_new0 (WriteHandle); handle->fh = data; g_vfs_job_open_for_write_set_handle (op_job, handle); g_vfs_job_open_for_write_set_can_seek (op_job, TRUE); g_vfs_job_open_for_write_set_can_truncate (op_job, TRUE); g_vfs_job_succeeded (job); } else { g_vfs_job_failed_from_errno (job, -err); } } static gboolean try_append_to (GVfsBackend *backend, GVfsJobOpenForWrite *job, const char *filename, GFileCreateFlags flags) { GVfsBackendNfs *op_backend = G_VFS_BACKEND_NFS (backend); nfs_create_async (op_backend->ctx, filename, O_APPEND, (flags & G_FILE_CREATE_PRIVATE ? 0600 : 0666) & ~op_backend->umask, append_cb, job); return TRUE; } /* The following types and functions implement an asynchronous copy which calls * a callback function with a boolean success or failure. This is used in some * cases for backup files when replacing. */ #define COPY_BLKSIZE (64 * 1024) typedef void (*CopyFileCallback) (gboolean success, void *private_data); typedef struct { struct nfsfh *srcfh; struct nfsfh *destfh; char *dest; int mode; CopyFileCallback cb; void *private_data; } CopyHandle; static void copy_handle_complete (struct nfs_context *ctx, CopyHandle *handle, gboolean result) { if (handle->srcfh) nfs_close_async (ctx, handle->srcfh, null_cb, NULL); if (handle->destfh) nfs_close_async (ctx, handle->destfh, null_cb, NULL); handle->cb (result, handle->private_data); g_slice_free (CopyHandle, handle); } static void copy_read_cb (int err, struct nfs_context *ctx, void *data, void *private_data); static void copy_write_cb (int err, struct nfs_context *ctx, void *data, void *private_data) { CopyHandle *handle = private_data; if (err > 0) nfs_read_async (ctx, handle->srcfh, COPY_BLKSIZE, copy_read_cb, handle); else copy_handle_complete (ctx, handle, FALSE); } static void copy_read_cb (int err, struct nfs_context *ctx, void *data, void *private_data) { CopyHandle *handle = private_data; if (err == 0) copy_handle_complete (ctx, handle, TRUE); else if (err > 0) nfs_write_async (ctx, handle->destfh, err, data, copy_write_cb, handle); else copy_handle_complete (ctx, handle, FALSE); } static void copy_open_dest_cb (int err, struct nfs_context *ctx, void *data, void *private_data) { CopyHandle *handle = private_data; if (err == 0) { handle->destfh = data; nfs_read_async (ctx, handle->srcfh, COPY_BLKSIZE, copy_read_cb, handle); } else { copy_handle_complete (ctx, handle, FALSE); } } static void copy_open_source_cb (int err, struct nfs_context *ctx, void *data, void *private_data) { CopyHandle *handle = private_data; if (err == 0) { handle->srcfh = data; nfs_create_async (ctx, handle->dest, O_TRUNC, handle->mode & 0777, copy_open_dest_cb, handle); g_free (handle->dest); } else { g_free (handle->dest); copy_handle_complete (ctx, handle, FALSE); } } static void copy_file (struct nfs_context *ctx, const char *src, const char *dest, int mode, CopyFileCallback cb, void *private_data) { CopyHandle *handle; handle = g_slice_new0 (CopyHandle); handle->dest = g_strdup (dest); handle->mode = mode; handle->cb = cb; handle->private_data = private_data; nfs_open_async (ctx, src, O_RDONLY, copy_open_source_cb, handle); } /* The replace code that follows is relatively straightforward but difficult to * read due to its asynchronous nature. It closely follows the replace * implementation for local files in GIO, but in an async fashion. * Firstly, the file is opened with O_EXCL. If this fails, the file exists. * Some checks on the existing file are made. If possible, a temporary file is * opened and renamed over the existing file on close. If a backup is needed, * the existing file is simply renamed to the backup. * If it is not possible to create a temporary file (e.g. due to permissions), * then the file is truncated and writing takes place directly into the file. * However, if a backup needs to be made, the existing file is first copied * into the backup file. */ static void replace_trunc_cb (int err, struct nfs_context *ctx, void *data, void *private_data) { WriteHandle *handle = private_data; GVfsJob *job = handle->job; if (err == 0) { GVfsJobOpenForWrite *op_job = G_VFS_JOB_OPEN_FOR_WRITE (job); handle->fh = data; g_vfs_job_open_for_write_set_handle (op_job, handle); g_vfs_job_open_for_write_set_can_seek (op_job, TRUE); g_vfs_job_open_for_write_set_can_truncate (op_job, TRUE); g_vfs_job_succeeded (job); g_object_unref (handle->job); handle->job = NULL; } else { g_vfs_job_failed_from_errno (job, -err); write_handle_free (handle); } } static void replace_backup_chown_cb (int err, struct nfs_context *ctx, void *data, void *private_data) { WriteHandle *handle = private_data; GVfsJob *job = handle->job; g_free (handle->backup_filename); handle->backup_filename = NULL; if (err == 0 || err == -EPERM) { GVfsJobOpenForWrite *op_job = G_VFS_JOB_OPEN_FOR_WRITE (job); GVfsBackendNfs *op_backend = G_VFS_BACKEND_NFS (op_job->backend); nfs_create_async (op_backend->ctx, op_job->filename, O_TRUNC, (op_job->flags & G_FILE_CREATE_PRIVATE ? 0600 : 0666) & ~op_backend->umask, replace_trunc_cb, handle); } else { g_vfs_job_failed_literal (job, G_IO_ERROR, G_IO_ERROR_CANT_CREATE_BACKUP, _("Backup file creation failed")); write_handle_free (handle); } } static void replace_backup_cb (gboolean success, void *private_data) { WriteHandle *handle = private_data; GVfsJob *job = handle->job; if (success) { GVfsJobOpenForWrite *op_job = G_VFS_JOB_OPEN_FOR_WRITE (job); GVfsBackendNfs *op_backend = G_VFS_BACKEND_NFS (op_job->backend); nfs_chown_async (op_backend->ctx, handle->backup_filename, handle->uid, handle->gid, replace_backup_chown_cb, handle); } else { g_vfs_job_failed_literal (job, G_IO_ERROR, G_IO_ERROR_CANT_CREATE_BACKUP, _("Backup file creation failed")); write_handle_free (handle); } } static void replace_rm_backup_cb (int err, struct nfs_context *ctx, void *data, void *private_data) { WriteHandle *handle = private_data; GVfsJob *job = handle->job; if (err == 0 || err == -ENOENT || err == -EACCES) { GVfsJobOpenForWrite *op_job = G_VFS_JOB_OPEN_FOR_WRITE (job); copy_file (ctx, op_job->filename, handle->backup_filename, handle->mode & 0777, replace_backup_cb, handle); } else { g_vfs_job_failed_literal (job, G_IO_ERROR, G_IO_ERROR_CANT_CREATE_BACKUP, _("Backup file creation failed")); write_handle_free (handle); } } static void replace_truncate (struct nfs_context *ctx, WriteHandle *handle) { GVfsJob *job = handle->job; GVfsJobOpenForWrite *op_job = G_VFS_JOB_OPEN_FOR_WRITE (job); GVfsBackendNfs *op_backend = G_VFS_BACKEND_NFS (op_job->backend); g_free (handle->filename); g_free (handle->tempname); handle->filename = handle->tempname = NULL; if (op_job->make_backup) { handle->backup_filename = g_strconcat (op_job->filename, "~", NULL); nfs_unlink_async (ctx, handle->backup_filename, replace_rm_backup_cb, handle); } else { nfs_create_async (ctx, op_job->filename, O_TRUNC, (op_job->flags & G_FILE_CREATE_PRIVATE ? 0600 : 0666) & ~op_backend->umask, replace_trunc_cb, handle); } } static void replace_temp_chmod_cb (int err, struct nfs_context *ctx, void *data, void *private_data) { WriteHandle *handle = private_data; GVfsJob *job = handle->job; if (err == 0) { GVfsJobOpenForWrite *op_job = G_VFS_JOB_OPEN_FOR_WRITE (job); g_vfs_job_open_for_write_set_handle (op_job, handle); g_vfs_job_open_for_write_set_can_seek (op_job, TRUE); g_vfs_job_open_for_write_set_can_truncate (op_job, TRUE); g_vfs_job_succeeded (job); g_object_unref (handle->job); handle->job = NULL; } else { nfs_close_async (ctx, handle->fh, null_cb, NULL); g_vfs_job_failed_literal (job, G_IO_ERROR, G_IO_ERROR_FAILED, _("Unable to create temporary file")); write_handle_free (handle); } } static void replace_temp_chown_cb (int err, struct nfs_context *ctx, void *data, void *private_data) { WriteHandle *handle = private_data; if (err == 0) { nfs_fchmod_async (ctx, handle->fh, handle->mode & 0777, replace_temp_chmod_cb, handle); } else { nfs_close_async (ctx, handle->fh, null_cb, NULL); g_vfs_job_failed_literal (handle->job, G_IO_ERROR, G_IO_ERROR_FAILED, _("Unable to create temporary file")); write_handle_free (handle); } } static void replace_temp_cb (int err, struct nfs_context *ctx, void *data, void *private_data) { WriteHandle *handle = private_data; GVfsJob *job = handle->job; GVfsJobOpenForWrite *op_job = G_VFS_JOB_OPEN_FOR_WRITE (job); if (err == 0) { handle->fh = data; if (op_job->make_backup) handle->backup_filename = g_strconcat (op_job->filename, "~", NULL); if (op_job->flags & G_FILE_CREATE_REPLACE_DESTINATION) { g_vfs_job_open_for_write_set_handle (op_job, handle); g_vfs_job_open_for_write_set_can_seek (op_job, TRUE); g_vfs_job_open_for_write_set_can_truncate (op_job, TRUE); g_vfs_job_succeeded (job); g_object_unref (handle->job); handle->job = NULL; } else { nfs_fchown_async (ctx, handle->fh, handle->uid, handle->gid, replace_temp_chown_cb, handle); } } else if ((err == -EACCES || err == -EEXIST) && !(op_job->flags & G_FILE_CREATE_REPLACE_DESTINATION)) { replace_truncate (ctx, handle); } else if (err == -EEXIST) { g_vfs_job_failed_literal (job, G_IO_ERROR, G_IO_ERROR_FAILED, _("Unable to create temporary file")); write_handle_free (handle); } else { g_vfs_job_failed_from_errno (job, -err); write_handle_free (handle); } } static void replace_stat_cb (int err, struct nfs_context *ctx, void *data, void *private_data) { WriteHandle *handle = private_data; GVfsJob *job = handle->job; if (err == 0) { GVfsJobOpenForWrite *op_job = G_VFS_JOB_OPEN_FOR_WRITE (job); GVfsBackendNfs *op_backend = G_VFS_BACKEND_NFS (op_job->backend); struct nfs_stat_64 *st = data; /* Fail if we're not replacing the destination and the destination is a * directory, or if we are replacing the destination and the destination * is a directory */ if ((!(op_job->flags & G_FILE_CREATE_REPLACE_DESTINATION) || !handle->is_symlink) && S_ISDIR (st->nfs_mode)) { g_vfs_job_failed_literal (job, G_IO_ERROR, G_IO_ERROR_IS_DIRECTORY, _("Target file is a directory")); write_handle_free (handle); } /* Fail if we're not replacing the destination and the destination is not * a regular file. */ else if (!(op_job->flags & G_FILE_CREATE_REPLACE_DESTINATION) && !S_ISREG (st->nfs_mode)) { g_vfs_job_failed_literal (job, G_IO_ERROR, G_IO_ERROR_NOT_REGULAR_FILE, _("Target file is not a regular file")); write_handle_free (handle); } else { if (op_job->etag) { char *etag; etag = create_etag (st->nfs_mtime, st->nfs_mtime_nsec); if (strcmp (etag, op_job->etag)) { g_free (etag); g_vfs_job_failed_literal (job, G_IO_ERROR, G_IO_ERROR_WRONG_ETAG, _("The file was externally modified")); write_handle_free (handle); return; } g_free (etag); } handle->mode = st->nfs_mode; handle->uid = st->nfs_uid; handle->gid = st->nfs_gid; handle->nlink = st->nfs_nlink; /* Write to a temporary file then do an atomic rename if either: * - G_FILE_CREATE_REPLACE_DESTINATION is specified * - the destination is not a symlink, and it does not have multiple * hard links. */ if ((op_job->flags & G_FILE_CREATE_REPLACE_DESTINATION) || (!handle->is_symlink && handle->nlink <= 1)) { char *dirname; char basename[] = ".giosaveXXXXXX"; handle->filename = g_strdup (op_job->filename); dirname = g_path_get_dirname (op_job->filename); gvfs_randomize_string (basename + 8, 6); handle->tempname = g_build_filename (dirname, basename, NULL); g_free (dirname); nfs_create_async (ctx, handle->tempname, O_EXCL, (op_job->flags & G_FILE_CREATE_PRIVATE ? 0600 : 0666) & ~op_backend->umask, replace_temp_cb, handle); } else { replace_truncate (ctx, handle); } } } else { g_vfs_job_failed_from_errno (job, -err); write_handle_free (handle); } } static void replace_lstat_cb (int err, struct nfs_context *ctx, void *data, void *private_data) { GVfsJob *job = G_VFS_JOB (private_data); if (err == 0) { WriteHandle *handle; GVfsJobOpenForWrite *op_job = G_VFS_JOB_OPEN_FOR_WRITE (job); struct nfs_stat_64 *st = data; handle = g_slice_new0 (WriteHandle); handle->is_symlink = S_ISLNK (st->nfs_mode); handle->job = g_object_ref (job); /* If the filename is a link, call stat to get the real info. * Otherwise, lstat is the same as stat, so just chain straight to the * stat callback. */ if (handle->is_symlink) nfs_stat64_async (ctx, op_job->filename, replace_stat_cb, handle); else replace_stat_cb (err, ctx, data, handle); } else { g_vfs_job_failed_from_errno (job, -err); } } static void replace_create_cb (int err, struct nfs_context *ctx, void *data, void *private_data) { GVfsJob *job = G_VFS_JOB (private_data); GVfsJobOpenForWrite *op_job = G_VFS_JOB_OPEN_FOR_WRITE (job); if (err == 0) { WriteHandle *handle = g_slice_new0 (WriteHandle); handle->fh = data; g_vfs_job_open_for_write_set_handle (op_job, handle); g_vfs_job_open_for_write_set_can_seek (op_job, TRUE); g_vfs_job_open_for_write_set_can_truncate (op_job, TRUE); g_vfs_job_succeeded (job); } else if (err == -EEXIST) { nfs_lstat64_async (ctx, op_job->filename, replace_lstat_cb, job); } else { g_vfs_job_failed_from_errno (job, -err); } } static gboolean try_replace (GVfsBackend *backend, GVfsJobOpenForWrite *job, const char *filename, const char *etag, gboolean make_backup, GFileCreateFlags flags) { GVfsBackendNfs *op_backend = G_VFS_BACKEND_NFS (backend); nfs_create_async (op_backend->ctx, filename, O_EXCL, (flags & G_FILE_CREATE_PRIVATE ? 0600 : 0666) & ~op_backend->umask, replace_create_cb, job); return TRUE; } static void create_cb (int err, struct nfs_context *ctx, void *data, void *private_data) { GVfsJob *job = G_VFS_JOB (private_data); if (err == 0) { GVfsJobOpenForWrite *op_job = G_VFS_JOB_OPEN_FOR_WRITE (job); WriteHandle *handle = g_slice_new0 (WriteHandle); handle->fh = data; g_vfs_job_open_for_write_set_handle (op_job, handle); g_vfs_job_open_for_write_set_can_seek (op_job, TRUE); g_vfs_job_open_for_write_set_can_truncate (op_job, TRUE); g_vfs_job_succeeded (job); } else { g_vfs_job_failed_from_errno (job, -err); } } static gboolean try_create (GVfsBackend *backend, GVfsJobOpenForWrite *job, const char *filename, GFileCreateFlags flags) { GVfsBackendNfs *op_backend = G_VFS_BACKEND_NFS (backend); nfs_create_async (op_backend->ctx, filename, O_EXCL, (flags & G_FILE_CREATE_PRIVATE ? 0600 : 0666) & ~op_backend->umask, create_cb, job); return TRUE; } static void write_cb (int err, struct nfs_context *ctx, void *data, void *private_data) { GVfsJob *job = G_VFS_JOB (private_data); if (err >= 0) { g_vfs_job_write_set_written_size (G_VFS_JOB_WRITE (job), err); g_vfs_job_succeeded (job); } else { g_vfs_job_failed_from_errno (job, -err); } } static gboolean try_write (GVfsBackend *backend, GVfsJobWrite *job, GVfsBackendHandle _handle, char *buffer, gsize buffer_size) { GVfsBackendNfs *op_backend = G_VFS_BACKEND_NFS (backend); WriteHandle *handle = _handle; struct nfsfh *fh = handle->fh; nfs_write_async (op_backend->ctx, fh, buffer_size, buffer, write_cb, job); return TRUE; } static void close_backup_cb (int err, struct nfs_context *ctx, void *data, void *private_data) { WriteHandle *handle = private_data; GVfsJob *job = handle->job; if (err == 0) { nfs_rename_async (ctx, handle->tempname, handle->filename, generic_cb, job); } else { g_vfs_job_failed_literal (job, G_IO_ERROR, G_IO_ERROR_CANT_CREATE_BACKUP, _("Backup file creation failed")); } write_handle_free (handle); } static void close_write_cb (int err, struct nfs_context *ctx, void *data, void *private_data) { WriteHandle *handle = private_data; GVfsJob *job = handle->job; if (err == 0) { if (handle->backup_filename) { nfs_rename_async (ctx, handle->filename, handle->backup_filename, close_backup_cb, handle); } else { nfs_rename_async (ctx, handle->tempname, handle->filename, generic_cb, job); write_handle_free (handle); } } else { g_vfs_job_failed_from_errno (job, -err); write_handle_free (handle); } } static void close_stat_cb (int err, struct nfs_context *ctx, void *data, void *private_data) { WriteHandle *handle = private_data; GVfsJob *job = handle->job; if (err == 0) { GVfsJobCloseWrite *op_job = G_VFS_JOB_CLOSE_WRITE (job); char *etag; struct nfs_stat_64 *st = data; etag = create_etag (st->nfs_mtime, st->nfs_mtime_nsec); g_vfs_job_close_write_set_etag (op_job, etag); g_free (etag); } if (handle->tempname) { nfs_close_async (ctx, handle->fh, close_write_cb, handle); } else { nfs_close_async (ctx, handle->fh, generic_cb, job); write_handle_free (handle); } } static gboolean try_close_write (GVfsBackend *backend, GVfsJobCloseWrite *job, GVfsBackendHandle _handle) { GVfsBackendNfs *op_backend = G_VFS_BACKEND_NFS (backend); WriteHandle *handle = _handle; handle->job = G_VFS_JOB (g_object_ref (job)); nfs_fstat64_async (op_backend->ctx, handle->fh, close_stat_cb, handle); return TRUE; } static void query_info_on_write_cb (int err, struct nfs_context *ctx, void *data, void *private_data) { GVfsJob *job = G_VFS_JOB (private_data); if (err == 0) { GVfsJobQueryInfoWrite *op_job = G_VFS_JOB_QUERY_INFO_WRITE (job); struct nfs_stat_64 *st = data; set_info_from_stat (op_job->file_info, data, op_job->attribute_matcher); set_type_from_mode (op_job->file_info, st->nfs_mode); g_vfs_job_succeeded (job); } else { g_vfs_job_failed_from_errno (job, -err); } } static gboolean try_query_info_on_write (GVfsBackend *backend, GVfsJobQueryInfoWrite *job, GVfsBackendHandle _handle, GFileInfo *info, GFileAttributeMatcher *attribute_matcher) { GVfsBackendNfs *op_backend = G_VFS_BACKEND_NFS (backend); WriteHandle *handle = _handle; struct nfsfh *fh = handle->fh; nfs_fstat64_async (op_backend->ctx, fh, query_info_on_write_cb, job); return TRUE; } static void seek_on_write_cb (int err, struct nfs_context *ctx, void *data, void *private_data) { GVfsJob *job = G_VFS_JOB (private_data); if (err >= 0) { GVfsJobSeekWrite *op_job = G_VFS_JOB_SEEK_WRITE (job); uint64_t *pos = data; g_vfs_job_seek_write_set_offset (op_job, *pos); g_vfs_job_succeeded (job); } else { g_vfs_job_failed_from_errno (job, -err); } } static gboolean try_seek_on_write (GVfsBackend *backend, GVfsJobSeekWrite *job, GVfsBackendHandle _handle, goffset offset, GSeekType type) { GVfsBackendNfs *op_backend = G_VFS_BACKEND_NFS (backend); WriteHandle *handle = _handle; struct nfsfh *fh = handle->fh; nfs_lseek_async (op_backend->ctx, fh, offset, gvfs_seek_type_to_lseek (type), seek_on_write_cb, job); return TRUE; } static gboolean try_truncate (GVfsBackend *backend, GVfsJobTruncate *job, GVfsBackendHandle _handle, goffset size) { GVfsBackendNfs *op_backend = G_VFS_BACKEND_NFS (backend); WriteHandle *handle = _handle; struct nfsfh *fh = handle->fh; nfs_ftruncate_async (op_backend->ctx, fh, size, generic_cb, job); return TRUE; } static void query_fs_cb (int err, struct nfs_context *ctx, void *data, void *private_data) { GVfsJob *job = G_VFS_JOB (private_data); if (err == 0) { GVfsJobQueryFsInfo *op_job = G_VFS_JOB_QUERY_FS_INFO (job); struct statvfs *stat = data; /* If free and available are both 0, treat it like the size information * is missing. */ if (stat->f_bfree || stat->f_bavail) { g_file_info_set_attribute_uint64 (op_job->file_info, G_FILE_ATTRIBUTE_FILESYSTEM_FREE, stat->f_frsize * stat->f_bavail); g_file_info_set_attribute_uint64 (op_job->file_info, G_FILE_ATTRIBUTE_FILESYSTEM_SIZE, stat->f_frsize * stat->f_blocks); g_file_info_set_attribute_uint64 (op_job->file_info, G_FILE_ATTRIBUTE_FILESYSTEM_USED, stat->f_frsize * (stat->f_blocks - stat->f_bfree)); } g_file_info_set_attribute_boolean (op_job->file_info, G_FILE_ATTRIBUTE_FILESYSTEM_READONLY, stat->f_flag & ST_RDONLY); g_vfs_job_succeeded (job); } else { g_vfs_job_failed_from_errno (job, -err); } } static gboolean try_query_fs_info (GVfsBackend *backend, GVfsJobQueryFsInfo *job, const char *filename, GFileInfo *info, GFileAttributeMatcher *matcher) { GVfsBackendNfs *op_backend = G_VFS_BACKEND_NFS (backend); g_file_info_set_attribute_string (info, G_FILE_ATTRIBUTE_FILESYSTEM_TYPE, "nfs"); g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_FILESYSTEM_REMOTE, TRUE); g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_FILESYSTEM_USE_PREVIEW, G_FILESYSTEM_PREVIEW_TYPE_IF_ALWAYS); if (g_file_attribute_matcher_matches (matcher, G_FILE_ATTRIBUTE_FILESYSTEM_SIZE) || g_file_attribute_matcher_matches (matcher, G_FILE_ATTRIBUTE_FILESYSTEM_FREE) || g_file_attribute_matcher_matches (matcher, G_FILE_ATTRIBUTE_FILESYSTEM_USED) || g_file_attribute_matcher_matches (matcher, G_FILE_ATTRIBUTE_FILESYSTEM_READONLY)) { nfs_statvfs_async (op_backend->ctx, filename, query_fs_cb, job); } else { g_vfs_job_succeeded (G_VFS_JOB (job)); } return TRUE; } typedef struct { GSList *readlink_list; GSList *symlink_list; GSList *access_list; gboolean requires_access; int access_parent; GVfsJobEnumerate *op_job; } EnumerateHandle; static void enumerate_continue (EnumerateHandle *handle, struct nfs_context *ctx); static void enumerate_access_cb (int err, struct nfs_context *ctx, void *data, void *private_data) { EnumerateHandle *handle = private_data; GFileInfo *info = handle->access_list->data; if (err >= 0) { g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_READ, err & R_OK); g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE, err & W_OK); g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE, err & X_OK); } g_vfs_job_enumerate_add_info (handle->op_job, info); g_object_unref (info); handle->access_list = g_slist_delete_link (handle->access_list, handle->access_list); enumerate_continue (handle, ctx); } static void enumerate_stat_cb (int err, struct nfs_context *ctx, void *data, void *private_data) { EnumerateHandle *handle = private_data; GFileInfo *info = handle->symlink_list->data; if (err == 0) { struct nfs_stat_64 *st = data; const char *mimetype; GFileInfo *new_info; new_info = g_file_info_new (); set_info_from_stat (new_info, st, handle->op_job->attribute_matcher); mimetype = set_type_from_mode (new_info, st->nfs_mode); set_name_info (new_info, mimetype, g_file_info_get_name (info), handle->op_job->attribute_matcher); g_file_info_set_is_symlink (new_info, TRUE); if (g_file_attribute_matcher_matches (handle->op_job->attribute_matcher, G_FILE_ATTRIBUTE_STANDARD_SYMLINK_TARGET)) g_file_info_set_symlink_target (new_info, g_file_info_get_symlink_target (info)); if ((g_file_attribute_matcher_matches (handle->op_job->attribute_matcher, G_FILE_ATTRIBUTE_ACCESS_CAN_DELETE) || g_file_attribute_matcher_matches (handle->op_job->attribute_matcher, G_FILE_ATTRIBUTE_ACCESS_CAN_RENAME)) && handle->access_parent >= 0) { g_file_info_set_attribute_boolean (new_info, G_FILE_ATTRIBUTE_ACCESS_CAN_DELETE, handle->access_parent & W_OK); g_file_info_set_attribute_boolean (new_info, G_FILE_ATTRIBUTE_ACCESS_CAN_RENAME, handle->access_parent & W_OK); } g_object_unref (info); info = new_info; } if (handle->requires_access) handle->access_list = g_slist_prepend (handle->access_list, info); else { g_vfs_job_enumerate_add_info (handle->op_job, info); g_object_unref (info); } handle->symlink_list = g_slist_delete_link (handle->symlink_list, handle->symlink_list); enumerate_continue (handle, ctx); } static void enumerate_readlink_cb (int err, struct nfs_context *ctx, void *data, void *private_data) { EnumerateHandle *handle = private_data; GFileInfo *info = handle->readlink_list->data; if (err == 0) g_file_info_set_symlink_target (info, data); if (!(handle->op_job->flags & G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS)) handle->symlink_list = g_slist_prepend (handle->symlink_list, info); else if (handle->requires_access) handle->access_list = g_slist_prepend (handle->access_list, info); else { g_vfs_job_enumerate_add_info (handle->op_job, info); g_object_unref (info); } handle->readlink_list = g_slist_delete_link (handle->readlink_list, handle->readlink_list); enumerate_continue (handle, ctx); } static void enumerate_continue (EnumerateHandle *handle, struct nfs_context *ctx) { if (handle->readlink_list || handle->symlink_list || handle->access_list) { char *path; if (handle->readlink_list) { const char *filename = g_file_info_get_name (handle->readlink_list->data); path = g_build_filename (handle->op_job->filename, filename, NULL); nfs_readlink_async (ctx, path, enumerate_readlink_cb, handle); } else if (handle->symlink_list) { const char *filename = g_file_info_get_name (handle->symlink_list->data); path = g_build_filename (handle->op_job->filename, filename, NULL); nfs_stat64_async (ctx, path, enumerate_stat_cb, handle); } else if (handle->access_list) { const char *filename = g_file_info_get_name (handle->access_list->data); path = g_build_filename (handle->op_job->filename, filename, NULL); nfs_access2_async (ctx, path, enumerate_access_cb, handle); } g_free (path); } else { GVfsJobEnumerate *op_job = handle->op_job; g_slice_free (EnumerateHandle, handle); g_vfs_job_enumerate_done (op_job); } } static void enumerate_cb (int err, struct nfs_context *ctx, void *data, void *private_data) { EnumerateHandle *handle = private_data; GVfsJob *job = G_VFS_JOB (handle->op_job); if (err == 0) { GVfsJobEnumerate *op_job = handle->op_job; struct nfsdir *dir = data; struct nfsdirent *d; g_vfs_job_succeeded (job); handle->requires_access = g_file_attribute_matcher_matches (op_job->attribute_matcher, G_FILE_ATTRIBUTE_ACCESS_CAN_READ) || g_file_attribute_matcher_matches (op_job->attribute_matcher, G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE) || g_file_attribute_matcher_matches (op_job->attribute_matcher, G_FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE); while ((d = nfs_readdir (ctx, dir))) { GFileInfo *info; GFileType type = G_FILE_TYPE_UNKNOWN; char *etag, *mimetype = NULL; if (!strcmp (d->name, ".") || !strcmp (d->name, "..")) continue; info = g_file_info_new (); g_file_info_set_size (info, d->size); g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_UID, d->uid); g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_GID, d->gid); g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_MODE, d->mode); g_file_info_set_attribute_uint64 (info, G_FILE_ATTRIBUTE_UNIX_INODE, d->inode); g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_NLINK, d->nlink); g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_DEVICE, d->dev); g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_RDEV, d->rdev); g_file_info_set_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_ACCESS, d->atime.tv_sec); g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_TIME_ACCESS_USEC, d->atime.tv_usec); g_file_info_set_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_MODIFIED, d->mtime.tv_sec); g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_TIME_MODIFIED_USEC, d->mtime.tv_usec); g_file_info_set_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_CHANGED, d->ctime.tv_sec); g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_TIME_CHANGED_USEC, d->ctime.tv_usec); g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_BLOCK_SIZE, d->blksize); g_file_info_set_attribute_uint64 (info, G_FILE_ATTRIBUTE_UNIX_BLOCKS, d->blocks); g_file_info_set_attribute_uint64 (info, G_FILE_ATTRIBUTE_STANDARD_ALLOCATED_SIZE, d->used); g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_TRASH, FALSE); etag = create_etag (d->mtime.tv_sec, d->mtime_nsec); g_file_info_set_attribute_string (info, G_FILE_ATTRIBUTE_ETAG_VALUE, etag); g_free (etag); switch (d->type) { case NF3REG: type = G_FILE_TYPE_REGULAR; break; case NF3DIR: type = G_FILE_TYPE_DIRECTORY; mimetype = "inode/directory"; break; case NF3BLK: type = G_FILE_TYPE_SPECIAL; mimetype = "inode/blockdevice"; break; case NF3CHR: type = G_FILE_TYPE_SPECIAL; mimetype = "inode/chardevice"; break; case NF3SOCK: type = G_FILE_TYPE_SPECIAL; mimetype = "inode/socket"; break; case NF3FIFO: type = G_FILE_TYPE_SPECIAL; mimetype = "inode/fifo"; break; case NF3LNK: type = G_FILE_TYPE_SYMBOLIC_LINK; mimetype = "inode/symlink"; g_file_info_set_is_symlink (info, TRUE); break; } g_file_info_set_file_type (info, type); set_name_info (info, mimetype, d->name, op_job->attribute_matcher); if ((g_file_attribute_matcher_matches (op_job->attribute_matcher, G_FILE_ATTRIBUTE_ACCESS_CAN_DELETE) || g_file_attribute_matcher_matches (op_job->attribute_matcher, G_FILE_ATTRIBUTE_ACCESS_CAN_RENAME)) && handle->access_parent >= 0) { g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_DELETE, handle->access_parent & W_OK); g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_RENAME, handle->access_parent & W_OK); } if (d->type == NF3LNK && g_file_attribute_matcher_matches (op_job->attribute_matcher, G_FILE_ATTRIBUTE_STANDARD_SYMLINK_TARGET)) { handle->readlink_list = g_slist_prepend (handle->readlink_list, info); continue; } if (d->type == NF3LNK && !(op_job->flags & G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS)) { handle->symlink_list = g_slist_prepend (handle->symlink_list, info); continue; } if (handle->requires_access) { handle->access_list = g_slist_prepend (handle->access_list, info); continue; } g_vfs_job_enumerate_add_info (op_job, info); g_object_unref (info); } nfs_closedir (ctx, dir); enumerate_continue (handle, ctx); } else { g_slice_free (EnumerateHandle, handle); g_vfs_job_failed_from_errno (job, -err); } } static void enumerate_access_parent_cb (int err, struct nfs_context *ctx, void *data, void *private_data) { EnumerateHandle *handle = private_data; GVfsJobEnumerate *op_job = handle->op_job; handle->access_parent = err; nfs_opendir_async (ctx, op_job->filename, enumerate_cb, handle); } static gboolean try_enumerate (GVfsBackend *backend, GVfsJobEnumerate *job, const char *filename, GFileAttributeMatcher *attribute_matcher, GFileQueryInfoFlags flags) { GVfsBackendNfs *op_backend = G_VFS_BACKEND_NFS (backend); EnumerateHandle *handle; handle = g_slice_new0 (EnumerateHandle); handle->op_job = job; handle->access_parent = -1; if (g_file_attribute_matcher_matches (attribute_matcher, G_FILE_ATTRIBUTE_ACCESS_CAN_DELETE) || g_file_attribute_matcher_matches (attribute_matcher, G_FILE_ATTRIBUTE_ACCESS_CAN_RENAME)) { nfs_access2_async (op_backend->ctx, filename, enumerate_access_parent_cb, handle); } else { nfs_opendir_async (op_backend->ctx, filename, enumerate_cb, handle); } return TRUE; } static void stat_access_parent_cb (int err, struct nfs_context *ctx, void *data, void *private_data); static void stat_readlink_cb (int err, struct nfs_context *ctx, void *data, void *private_data); static void stat_access_cb (int err, struct nfs_context *ctx, void *data, void *private_data) { GVfsJob *job = G_VFS_JOB (private_data); GVfsJobQueryInfo *op_job = G_VFS_JOB_QUERY_INFO (job); GFileInfo *info = op_job->file_info; if (err >= 0) { g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_READ, err & R_OK); g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE, err & W_OK); g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE, err & X_OK); } if (g_file_attribute_matcher_matches (op_job->attribute_matcher, G_FILE_ATTRIBUTE_ACCESS_CAN_RENAME) || g_file_attribute_matcher_matches (op_job->attribute_matcher, G_FILE_ATTRIBUTE_ACCESS_CAN_DELETE)) { char *dirname = g_path_get_dirname (op_job->filename); nfs_access2_async (ctx, dirname, stat_access_parent_cb, job); g_free (dirname); } else if (g_file_attribute_matcher_matches (op_job->attribute_matcher, G_FILE_ATTRIBUTE_STANDARD_SYMLINK_TARGET)) { nfs_readlink_async (ctx, op_job->filename, stat_readlink_cb, job); } else { g_vfs_job_succeeded (job); } } static void stat_access_parent_cb (int err, struct nfs_context *ctx, void *data, void *private_data) { GVfsJob *job = G_VFS_JOB (private_data); GVfsJobQueryInfo *op_job = G_VFS_JOB_QUERY_INFO (job); GFileInfo *info = op_job->file_info; if (err >= 0) { g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_RENAME, err & W_OK); g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_DELETE, err & W_OK); } if (g_file_attribute_matcher_matches (op_job->attribute_matcher, G_FILE_ATTRIBUTE_STANDARD_SYMLINK_TARGET)) nfs_readlink_async (ctx, op_job->filename, stat_readlink_cb, job); else g_vfs_job_succeeded (job); } static void stat_readlink_cb (int err, struct nfs_context *ctx, void *data, void *private_data) { GVfsJob *job = G_VFS_JOB (private_data); GVfsJobQueryInfo *op_job = G_VFS_JOB_QUERY_INFO (job); GFileInfo *info = op_job->file_info; if (err == 0) g_file_info_set_symlink_target (info, data); g_vfs_job_succeeded (job); } static void stat_cb (int err, struct nfs_context *ctx, void *data, void *private_data) { GVfsJob *job = G_VFS_JOB (private_data); if (err == 0) { GVfsJobQueryInfo *op_job = G_VFS_JOB_QUERY_INFO (job); GFileInfo *info = op_job->file_info; struct nfs_stat_64 *st = data; const char *mimetype; char *basename, *etag; set_info_from_stat (info, st, op_job->attribute_matcher); etag = create_etag (st->nfs_mtime, st->nfs_mtime_nsec); g_file_info_set_attribute_string (info, G_FILE_ATTRIBUTE_ETAG_VALUE, etag); g_free (etag); mimetype = set_type_from_mode (info, st->nfs_mode); if (!strcmp (op_job->filename, "/")) { GMountSpec *mount_spec = g_vfs_backend_get_mount_spec (op_job->backend); basename = g_path_get_basename (mount_spec->mount_prefix); } else { basename = g_path_get_basename (op_job->filename); } set_name_info (info, mimetype, basename, op_job->attribute_matcher); g_free (basename); if (g_file_attribute_matcher_matches (op_job->attribute_matcher, G_FILE_ATTRIBUTE_ACCESS_CAN_READ) || g_file_attribute_matcher_matches (op_job->attribute_matcher, G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE) || g_file_attribute_matcher_matches (op_job->attribute_matcher, G_FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE)) { nfs_access2_async (ctx, op_job->filename, stat_access_cb, job); } else if (g_file_attribute_matcher_matches (op_job->attribute_matcher, G_FILE_ATTRIBUTE_ACCESS_CAN_RENAME) || g_file_attribute_matcher_matches (op_job->attribute_matcher, G_FILE_ATTRIBUTE_ACCESS_CAN_DELETE)) { char *dirname = g_path_get_dirname (op_job->filename); nfs_access2_async (ctx, dirname, stat_access_parent_cb, job); g_free (dirname); } else if (g_file_attribute_matcher_matches (op_job->attribute_matcher, G_FILE_ATTRIBUTE_STANDARD_SYMLINK_TARGET)) { nfs_readlink_async (ctx, op_job->filename, stat_readlink_cb, job); } else { g_vfs_job_succeeded (job); } } else { g_vfs_job_failed_from_errno (job, -err); } } static void stat_is_symlink_cb (int err, struct nfs_context *ctx, void *data, void *private_data) { GVfsJob *job = G_VFS_JOB (private_data); if (err == 0) { GVfsJobQueryInfo *op_job = G_VFS_JOB_QUERY_INFO (job); struct nfs_stat_64 *st = data; /* In the case that symlinks are not followed, this is set by * set_type_from_mode in stat_cb(). */ g_file_info_set_is_symlink (op_job->file_info, S_ISLNK (st->nfs_mode)); /* If the filename is a link, call stat to get the real info. * Otherwise, lstat is the same as stat, so just chain straight to the * stat callback. */ if (S_ISLNK (st->nfs_mode)) nfs_stat64_async (ctx, op_job->filename, stat_cb, job); else stat_cb (err, ctx, data, private_data); } else { g_vfs_job_failed_from_errno (job, -err); } } static gboolean try_query_info (GVfsBackend *backend, GVfsJobQueryInfo *job, const char *filename, GFileQueryInfoFlags flags, GFileInfo *info, GFileAttributeMatcher *matcher) { GVfsBackendNfs *op_backend = G_VFS_BACKEND_NFS (backend); if (flags & G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS) nfs_lstat64_async (op_backend->ctx, filename, stat_cb, job); else nfs_lstat64_async (op_backend->ctx, filename, stat_is_symlink_cb, job); return TRUE; } static gboolean try_set_display_name (GVfsBackend *backend, GVfsJobSetDisplayName *job, const char *filename, const char *display_name) { GVfsBackendNfs *op_backend = G_VFS_BACKEND_NFS (backend); char *dirname, *basename, *new_name; dirname = g_path_get_dirname (filename); basename = g_filename_from_utf8 (display_name, -1, NULL, NULL, NULL); if (basename == NULL) basename = g_strdup (display_name); new_name = g_build_filename (dirname, basename, NULL); g_free (dirname); g_free (basename); g_vfs_job_set_display_name_set_new_path (job, new_name); nfs_rename_async (op_backend->ctx, filename, new_name, generic_cb, job); g_free (new_name); return TRUE; } static gboolean try_query_settable_attributes (GVfsBackend *backend, GVfsJobQueryAttributes *job, const char *filename) { GFileAttributeInfoList *list; list = g_file_attribute_info_list_new (); g_file_attribute_info_list_add (list, G_FILE_ATTRIBUTE_TIME_ACCESS, G_FILE_ATTRIBUTE_TYPE_UINT64, G_FILE_ATTRIBUTE_INFO_COPY_WHEN_MOVED); g_file_attribute_info_list_add (list, G_FILE_ATTRIBUTE_TIME_ACCESS_USEC, G_FILE_ATTRIBUTE_TYPE_UINT32, G_FILE_ATTRIBUTE_INFO_COPY_WHEN_MOVED); g_file_attribute_info_list_add (list, G_FILE_ATTRIBUTE_TIME_MODIFIED, G_FILE_ATTRIBUTE_TYPE_UINT64, G_FILE_ATTRIBUTE_INFO_COPY_WITH_FILE | G_FILE_ATTRIBUTE_INFO_COPY_WHEN_MOVED); g_file_attribute_info_list_add (list, G_FILE_ATTRIBUTE_TIME_MODIFIED_USEC, G_FILE_ATTRIBUTE_TYPE_UINT32, G_FILE_ATTRIBUTE_INFO_COPY_WITH_FILE | G_FILE_ATTRIBUTE_INFO_COPY_WHEN_MOVED); g_file_attribute_info_list_add (list, G_FILE_ATTRIBUTE_UNIX_UID, G_FILE_ATTRIBUTE_TYPE_UINT32, G_FILE_ATTRIBUTE_INFO_COPY_WHEN_MOVED); g_file_attribute_info_list_add (list, G_FILE_ATTRIBUTE_UNIX_GID, G_FILE_ATTRIBUTE_TYPE_UINT32, G_FILE_ATTRIBUTE_INFO_COPY_WHEN_MOVED); g_file_attribute_info_list_add (list, G_FILE_ATTRIBUTE_UNIX_MODE, G_FILE_ATTRIBUTE_TYPE_UINT32, G_FILE_ATTRIBUTE_INFO_COPY_WITH_FILE | G_FILE_ATTRIBUTE_INFO_COPY_WHEN_MOVED); g_vfs_job_query_attributes_set_list (job, list); g_vfs_job_succeeded (G_VFS_JOB (job)); g_file_attribute_info_list_unref (list); return TRUE; } static void set_mod_cb (int err, struct nfs_context *ctx, void *data, void *private_data) { GVfsJob *job = G_VFS_JOB (private_data); if (err == 0) { GVfsJobSetAttribute *op_job = G_VFS_JOB_SET_ATTRIBUTE (job); struct nfs_stat_64 *st = data; gpointer *value_p = _g_dbus_attribute_as_pointer (op_job->type, &op_job->value); struct timeval tv[2]; if (!strcmp (op_job->attribute, G_FILE_ATTRIBUTE_TIME_ACCESS)) tv[0].tv_sec = *(guint64 *)value_p; else tv[0].tv_sec = st->nfs_atime; if (!strcmp (op_job->attribute, G_FILE_ATTRIBUTE_TIME_ACCESS_USEC)) tv[0].tv_usec = *(guint32 *)value_p; else tv[0].tv_usec = st->nfs_atime_nsec / 1000; if (!strcmp (op_job->attribute, G_FILE_ATTRIBUTE_TIME_MODIFIED)) tv[1].tv_sec = *(guint64 *)value_p; else tv[1].tv_sec = st->nfs_mtime; if (!strcmp (op_job->attribute, G_FILE_ATTRIBUTE_TIME_MODIFIED_USEC)) tv[1].tv_usec = *(guint32 *)value_p; else tv[1].tv_usec = st->nfs_mtime_nsec / 1000; if (op_job->flags & G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS) nfs_lutimes_async (ctx, op_job->filename, tv, generic_cb, job); else nfs_utimes_async (ctx, op_job->filename, tv, generic_cb, job); } else { g_vfs_job_failed_from_errno (job, -err); } } static gboolean try_set_attribute (GVfsBackend *backend, GVfsJobSetAttribute *job, const char *filename, const char *attribute, GFileAttributeType type, gpointer value_p, GFileQueryInfoFlags flags) { GVfsBackendNfs *op_backend = G_VFS_BACKEND_NFS (backend); if (!strcmp (attribute, G_FILE_ATTRIBUTE_TIME_ACCESS) || !strcmp (attribute, G_FILE_ATTRIBUTE_TIME_MODIFIED)) { if (type != G_FILE_ATTRIBUTE_TYPE_UINT64) goto error; if (flags & G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS) nfs_lstat64_async (op_backend->ctx, filename, set_mod_cb, job); else nfs_stat64_async (op_backend->ctx, filename, set_mod_cb, job); } else if (!strcmp (attribute, G_FILE_ATTRIBUTE_TIME_ACCESS_USEC) || !strcmp (attribute, G_FILE_ATTRIBUTE_TIME_MODIFIED_USEC)) { if (type != G_FILE_ATTRIBUTE_TYPE_UINT32) goto error; if (flags & G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS) nfs_lstat64_async (op_backend->ctx, filename, set_mod_cb, job); else nfs_stat64_async (op_backend->ctx, filename, set_mod_cb, job); } else if (!strcmp (attribute, G_FILE_ATTRIBUTE_UNIX_UID)) { if (type != G_FILE_ATTRIBUTE_TYPE_UINT32) goto error; if (flags & G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS) nfs_lchown_async (op_backend->ctx, filename, *(guint32 *)value_p, -1, generic_cb, job); else nfs_chown_async (op_backend->ctx, filename, *(guint32 *)value_p, -1, generic_cb, job); } else if (!strcmp (attribute, G_FILE_ATTRIBUTE_UNIX_GID)) { if (type != G_FILE_ATTRIBUTE_TYPE_UINT32) goto error; if (flags & G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS) nfs_lchown_async (op_backend->ctx, filename, -1, *(guint32 *)value_p, generic_cb, job); else nfs_chown_async (op_backend->ctx, filename, -1, *(guint32 *)value_p, generic_cb, job); } else if (!strcmp (attribute, G_FILE_ATTRIBUTE_UNIX_MODE)) { if (type != G_FILE_ATTRIBUTE_TYPE_UINT32) goto error; if (flags & G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS) nfs_lchmod_async (op_backend->ctx, filename, *(guint32 *)value_p & 0777, generic_cb, job); else nfs_chmod_async (op_backend->ctx, filename, *(guint32 *)value_p & 0777, generic_cb, job); } else { g_vfs_job_failed_literal (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, _("Operation not supported")); } return TRUE; error: g_vfs_job_failed_literal (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, _("Invalid attribute type")); return TRUE; } static gboolean try_unmount (GVfsBackend *backend, GVfsJobUnmount *job, GMountUnmountFlags flags, GMountSource *mount_source) { g_vfs_job_succeeded (G_VFS_JOB (job)); return TRUE; } typedef struct { GVfsJob *job; gboolean source_is_dir; uint64_t file_size; } MoveHandle; static void move_rename_cb (int err, struct nfs_context *ctx, void *data, void *private_data) { MoveHandle *handle = private_data; GVfsJob *job = handle->job; uint64_t file_size = handle->file_size; g_slice_free (MoveHandle, handle); if (err == 0) { g_vfs_job_progress_callback (file_size, file_size, job); g_vfs_job_succeeded (job); } else if (err == -EXDEV) { g_vfs_job_failed_literal (job, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, _("Operation not supported")); } else { g_vfs_job_failed_from_errno (job, -err); } } static void move_remove_cb (int err, struct nfs_context *ctx, void *data, void *private_data) { MoveHandle *handle = private_data; GVfsJob *job = handle->job; if (err == 0) { GVfsJobMove *op_job = G_VFS_JOB_MOVE (job); nfs_rename_async (ctx, op_job->source, op_job->destination, move_rename_cb, handle); } else { g_vfs_job_failed_from_errno (job, -err); g_slice_free (MoveHandle, handle); } } static void move_stat_dest_cb (int err, struct nfs_context *ctx, void *data, void *private_data) { MoveHandle *handle = private_data; GVfsJob *job = handle->job; GVfsJobMove *op_job = G_VFS_JOB_MOVE (job); if (err == 0) { struct nfs_stat_64 *st = data; if (op_job->flags & G_FILE_COPY_OVERWRITE) { if (S_ISDIR (st->nfs_mode)) { if (handle->source_is_dir) { g_vfs_job_failed_literal (job, G_IO_ERROR, G_IO_ERROR_WOULD_MERGE, _("Can’t move directory over directory")); } else { g_vfs_job_failed_literal (job, G_IO_ERROR, G_IO_ERROR_IS_DIRECTORY, _("File is directory")); } g_slice_free (MoveHandle, handle); return; } } else { g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_EXISTS, _("Target file already exists")); g_slice_free (MoveHandle, handle); return; } if (handle->source_is_dir && op_job->flags & G_FILE_COPY_OVERWRITE) { nfs_unlink_async (ctx, op_job->destination, move_remove_cb, handle); return; } } nfs_rename_async (ctx, op_job->source, op_job->destination, move_rename_cb, handle); } static void move_stat_source_cb (int err, struct nfs_context *ctx, void *data, void *private_data) { GVfsJob *job = G_VFS_JOB (private_data); if (err == 0) { GVfsJobMove *op_job = G_VFS_JOB_MOVE (job); struct nfs_stat_64 *st = data; MoveHandle *handle; handle = g_slice_new0 (MoveHandle); handle->job = job; handle->source_is_dir = S_ISDIR (st->nfs_mode); handle->file_size = st->nfs_size; nfs_lstat64_async (ctx, op_job->destination, move_stat_dest_cb, handle); } else { g_vfs_job_failed_from_errno (job, -err); } } static gboolean try_move (GVfsBackend *backend, GVfsJobMove *job, const char *source, const char *destination, GFileCopyFlags flags, GFileProgressCallback progress_callback, gpointer progress_callback_data) { GVfsBackendNfs *op_backend = G_VFS_BACKEND_NFS (backend); if (flags & G_FILE_COPY_BACKUP) { g_vfs_job_failed_literal (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, _("Operation not supported")); return TRUE; } nfs_lstat64_async (op_backend->ctx, source, move_stat_source_cb, job); return TRUE; } static void g_vfs_backend_nfs_class_init (GVfsBackendNfsClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); GVfsBackendClass *backend_class = G_VFS_BACKEND_CLASS (klass); gobject_class->finalize = g_vfs_backend_nfs_finalize; backend_class->mount = do_mount; backend_class->try_open_for_read = try_open_for_read; backend_class->try_read = try_read; backend_class->try_query_info_on_read = try_query_info_on_read; backend_class->try_seek_on_read = try_seek_on_read; backend_class->try_close_read = try_close_read; backend_class->try_make_directory = try_make_directory; backend_class->try_delete = try_delete; backend_class->try_make_symlink = try_make_symlink; backend_class->try_create = try_create; backend_class->try_append_to = try_append_to; backend_class->try_replace = try_replace; backend_class->try_write = try_write; backend_class->try_query_info_on_write = try_query_info_on_write; backend_class->try_seek_on_write = try_seek_on_write; backend_class->try_truncate = try_truncate; backend_class->try_close_write = try_close_write; backend_class->try_query_fs_info = try_query_fs_info; backend_class->try_enumerate = try_enumerate; backend_class->try_query_info = try_query_info; backend_class->try_set_display_name = try_set_display_name; backend_class->try_query_settable_attributes = try_query_settable_attributes; backend_class->try_set_attribute = try_set_attribute; backend_class->try_unmount = try_unmount; backend_class->try_move = try_move; }