diff options
Diffstat (limited to 'trunk/daemon/gvfsbackendsmb.c')
-rw-r--r-- | trunk/daemon/gvfsbackendsmb.c | 2102 |
1 files changed, 2102 insertions, 0 deletions
diff --git a/trunk/daemon/gvfsbackendsmb.c b/trunk/daemon/gvfsbackendsmb.c new file mode 100644 index 00000000..74b8f3bd --- /dev/null +++ b/trunk/daemon/gvfsbackendsmb.c @@ -0,0 +1,2102 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * + * 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, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Alexander Larsson <alexl@redhat.com> + */ + +#include <config.h> + +#include <sys/types.h> +#include <sys/stat.h> +#include <errno.h> +#include <unistd.h> +#include <fcntl.h> +#include <string.h> +#include <stdlib.h> + +#include <glib/gstdio.h> +#include <glib/gi18n.h> +#include <gio/gio.h> + +#include "gvfsbackendsmb.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 "gvfsjobqueryfsinfo.h" +#include "gvfsjobqueryattributes.h" +#include "gvfsjobenumerate.h" +#include "gvfsdaemonprotocol.h" +#include "gvfskeyring.h" + +#ifdef HAVE_GCONF +#include <gconf/gconf-client.h> +#endif + +/* We load a default workgroup from gconf */ +#define PATH_GCONF_GNOME_VFS_SMB_WORKGROUP "/system/smb/workgroup" + +#include <libsmbclient.h> +#include "libsmb-compat.h" + +struct _GVfsBackendSmb +{ + GVfsBackend parent_instance; + + char *server; + char *share; + char *user; + char *domain; + + SMBCCTX *smb_context; + + char *last_user; + char *last_domain; + char *last_password; + + GMountSource *mount_source; /* Only used/set during mount */ + int mount_try; + gboolean mount_try_again; + gboolean mount_cancelled; + + gboolean password_in_keyring; + GPasswordSave password_save; + + /* Cache */ + char *cached_server_name; + char *cached_share_name; + char *cached_domain; + char *cached_username; + SMBCSRV *cached_server; +}; + +static char *default_workgroup = NULL; + +G_DEFINE_TYPE (GVfsBackendSmb, g_vfs_backend_smb, G_VFS_TYPE_BACKEND) + +static void set_info_from_stat (GVfsBackendSmb *backend, + GFileInfo *info, + struct stat *statbuf, + const char *basename, + GFileAttributeMatcher *matcher); + + +static void +g_vfs_backend_smb_finalize (GObject *object) +{ + GVfsBackendSmb *backend; + + backend = G_VFS_BACKEND_SMB (object); + + g_free (backend->share); + g_free (backend->server); + g_free (backend->user); + g_free (backend->domain); + + if (G_OBJECT_CLASS (g_vfs_backend_smb_parent_class)->finalize) + (*G_OBJECT_CLASS (g_vfs_backend_smb_parent_class)->finalize) (object); +} + +static void +g_vfs_backend_smb_init (GVfsBackendSmb *backend) +{ +} + +/** + * Authentication callback function type (method that includes context) + * + * Type for the the authentication function called by the library to + * obtain authentication credentals + * + * @param context Pointer to the smb context + * @param srv Server being authenticated to + * @param shr Share being authenticated to + * @param wg Pointer to buffer containing a "hint" for the + * workgroup to be authenticated. Should be filled in + * with the correct workgroup if the hint is wrong. + * @param wglen The size of the workgroup buffer in bytes + * @param un Pointer to buffer containing a "hint" for the + * user name to be use for authentication. Should be + * filled in with the correct workgroup if the hint is + * wrong. + * @param unlen The size of the username buffer in bytes + * @param pw Pointer to buffer containing to which password + * copied + * @param pwlen The size of the password buffer in bytes + * + */ +static void +auth_callback (SMBCCTX *context, + const char *server_name, const char *share_name, + char *domain_out, int domainmaxlen, + char *username_out, int unmaxlen, + char *password_out, int pwmaxlen) +{ + GVfsBackendSmb *backend; + char *ask_password, *ask_user, *ask_domain; + gboolean handled, abort; + + backend = smbc_getOptionUserData (context); + + strncpy (password_out, "", pwmaxlen); + + if (backend->domain) + strncpy (domain_out, backend->domain, domainmaxlen); + if (backend->user) + strncpy (username_out, backend->user, unmaxlen); + + if (backend->mount_cancelled) + { + /* Don't prompt for credentials, let smbclient finish the mount loop */ + strncpy (username_out, "ABORT", unmaxlen); + strncpy (password_out, "", pwmaxlen); + return; + } + + if (backend->mount_source == NULL) + { + /* Not during mount, use last password */ + if (backend->last_user) + strncpy (username_out, backend->last_user, unmaxlen); + if (backend->last_domain) + strncpy (domain_out, backend->last_domain, domainmaxlen); + if (backend->last_password) + strncpy (password_out, backend->last_password, pwmaxlen); + + return; + } + + if (backend->mount_try == 0 && + backend->user == NULL && + backend->domain == NULL) + { + /* Try again if kerberos login + anonymous fallback fails */ + backend->mount_try_again = TRUE; + } + else + { + gboolean in_keyring = FALSE; + + if (!backend->password_in_keyring) + { + in_keyring = g_vfs_keyring_lookup_password (backend->user, + backend->server, + backend->domain, + "smb", + NULL, + NULL, + 0, + &ask_user, + &ask_domain, + &ask_password); + backend->password_in_keyring = in_keyring; + } + + if (!in_keyring) + { + GAskPasswordFlags flags = G_ASK_PASSWORD_NEED_PASSWORD; + char *message; + + if (g_vfs_keyring_is_available ()) + flags |= G_ASK_PASSWORD_SAVING_SUPPORTED; + if (backend->domain == NULL) + flags |= G_ASK_PASSWORD_NEED_DOMAIN; + if (backend->user == NULL) + flags |= G_ASK_PASSWORD_NEED_USERNAME; + + /* translators: First %s is a share name, second is a server name */ + message = g_strdup_printf (_("Password required for share %s on %s"), + share_name, server_name); + handled = g_mount_source_ask_password (backend->mount_source, + message, + username_out, + domain_out, + flags, + &abort, + &ask_password, + &ask_user, + &ask_domain, + NULL, + &(backend->password_save)); + g_free (message); + if (!handled) + goto out; + + if (abort) + { + strncpy (username_out, "ABORT", unmaxlen); + strncpy (password_out, "", pwmaxlen); + backend->mount_cancelled = TRUE; + goto out; + } + } + + /* Try again if this fails */ + backend->mount_try_again = TRUE; + + strncpy (password_out, ask_password, pwmaxlen); + if (ask_user && *ask_user) + strncpy (username_out, ask_user, unmaxlen); + if (ask_domain && *ask_domain) + strncpy (domain_out, ask_domain, domainmaxlen); + + out: + g_free (ask_password); + g_free (ask_user); + g_free (ask_domain); + } + + backend->last_user = g_strdup (username_out); + backend->last_domain = g_strdup (domain_out); + backend->last_password = g_strdup (password_out); +} + +/* Add a server to the cache system + * + * @param c pointer to smb context + * @param srv pointer to server to add + * @param server server name + * @param share share name + * @param workgroup workgroup used to connect + * @param username username used to connect + * @return 0 on success. 1 on failure. + * + */ +static int +add_cached_server (SMBCCTX *context, SMBCSRV *new, + const char *server_name, const char *share_name, + const char *domain, const char *username) +{ + GVfsBackendSmb *backend; + + backend = smbc_getOptionUserData (context); + + if (backend->cached_server != NULL) + return 1; + + backend->cached_server_name = g_strdup (server_name); + backend->cached_share_name = g_strdup (share_name); + backend->cached_domain = g_strdup (domain); + backend->cached_username = g_strdup (username); + backend->cached_server = new; + + return 0; +} + +/* Remove cached server + * + * @param c pointer to smb context + * @param srv pointer to server to remove + * @return 0 when found and removed. 1 on failure. + * + */ +static int +remove_cached_server(SMBCCTX * context, SMBCSRV * server) +{ + GVfsBackendSmb *backend; + + backend = smbc_getOptionUserData (context); + + if (backend->cached_server == server) + { + g_free (backend->cached_server_name); + backend->cached_server_name = NULL; + g_free (backend->cached_share_name); + backend->cached_share_name = NULL; + g_free (backend->cached_domain); + backend->cached_domain = NULL; + g_free (backend->cached_username); + backend->cached_username = NULL; + backend->cached_server = NULL; + return 0; + } + return 1; +} + + +/* Look up a server in the cache system + * + * @param c pointer to smb context + * @param server server name to match + * @param share share name to match + * @param workgroup workgroup to match + * @param username username to match + * @return pointer to SMBCSRV on success. NULL on failure. + * + */ +static SMBCSRV * +get_cached_server (SMBCCTX * context, + const char *server_name, const char *share_name, + const char *domain, const char *username) +{ + GVfsBackendSmb *backend; + + backend = smbc_getOptionUserData (context); + + if (backend->cached_server != NULL && + strcmp (backend->cached_server_name, server_name) == 0 && + strcmp (backend->cached_share_name, share_name) == 0 && + strcmp (backend->cached_domain, domain) == 0 && + strcmp (backend->cached_username, username) == 0) + return backend->cached_server; + + return NULL; +} + +/* Try to remove all servers from the cache system and disconnect + * + * @param c pointer to smb context + * + * @return 0 when found and removed. 1 on failure. + * + */ +static int +purge_cached (SMBCCTX * context) +{ + GVfsBackendSmb *backend; + + backend = smbc_getOptionUserData (context); + + if (backend->cached_server) + remove_cached_server(context, backend->cached_server); + + return 0; +} + +#define SUB_DELIM_CHARS "!$&'()*+,;=" + +static gboolean +is_valid (char c, const char *reserved_chars_allowed) +{ + if (g_ascii_isalnum (c) || + c == '-' || + c == '.' || + c == '_' || + c == '~') + return TRUE; + + if (reserved_chars_allowed && + strchr (reserved_chars_allowed, c) != NULL) + return TRUE; + + return FALSE; +} + +static void +g_string_append_encoded (GString *string, + const char *encoded, + const char *reserved_chars_allowed) +{ + char c; + static const gchar hex[16] = "0123456789ABCDEF"; + + while ((c = *encoded++) != 0) + { + if (is_valid (c, reserved_chars_allowed)) + g_string_append_c (string, c); + else + { + g_string_append_c (string, '%'); + g_string_append_c (string, hex[((guchar)c) >> 4]); + g_string_append_c (string, hex[((guchar)c) & 0xf]); + } + } +} + +static GString * +create_smb_uri_string (const char *server, + const char *share, + const char *path) +{ + GString *uri; + + uri = g_string_new ("smb://"); + g_string_append_encoded (uri, server, NULL); + g_string_append_c (uri, '/'); + g_string_append_encoded (uri, share, NULL); + if (path != NULL) + { + if (*path != '/') + g_string_append_c (uri, '/'); + g_string_append_encoded (uri, path, SUB_DELIM_CHARS ":@/"); + } + + while (uri->len > 0 && + uri->str[uri->len - 1] == '/') + g_string_erase (uri, uri->len - 1, 1); + + return uri; +} + +static char * +create_smb_uri (const char *server, + const char *share, + const char *path) +{ + GString *uri; + uri = create_smb_uri_string (server, share, path); + return g_string_free (uri, FALSE); +} + +static void +do_mount (GVfsBackend *backend, + GVfsJobMount *job, + GMountSpec *mount_spec, + GMountSource *mount_source, + gboolean is_automount) +{ + GVfsBackendSmb *op_backend = G_VFS_BACKEND_SMB (backend); + SMBCCTX *smb_context; + struct stat st; + char *uri; + int res; + char *display_name; + const char *debug; + int debug_val; + GMountSpec *smb_mount_spec; + smbc_stat_fn smbc_stat; + + smb_context = smbc_new_context (); + if (smb_context == NULL) + { + g_vfs_job_failed (G_VFS_JOB (job), + G_IO_ERROR, G_IO_ERROR_FAILED, + _("Internal Error (%s)"), "Failed to allocate smb context"); + return; + } + smbc_setOptionUserData (smb_context, backend); + + debug = g_getenv ("GVFS_SMB_DEBUG"); + if (debug) + debug_val = atoi (debug); + else + debug_val = 0; + + smbc_setDebug (smb_context, debug_val); + smbc_setFunctionAuthDataWithContext (smb_context, auth_callback); + + smbc_setFunctionAddCachedServer (smb_context, add_cached_server); + smbc_setFunctionGetCachedServer (smb_context, get_cached_server); + smbc_setFunctionRemoveCachedServer (smb_context, remove_cached_server); + smbc_setFunctionPurgeCachedServers (smb_context, purge_cached); + + /* FIXME: is strdup() still needed here? -- removed */ + if (default_workgroup != NULL) + smbc_setWorkgroup (smb_context, default_workgroup); + +#ifndef DEPRECATED_SMBC_INTERFACE + smb_context->flags = 0; +#endif + + /* Initial settings: + * - use Kerberos (always) + * - in case of no username specified, try anonymous login + */ + smbc_setOptionUseKerberos (smb_context, 1); + smbc_setOptionFallbackAfterKerberos (smb_context, + op_backend->user != NULL); + smbc_setOptionNoAutoAnonymousLogin (smb_context, + op_backend->user != NULL); + + +#if 0 + smbc_setOptionDebugToStderr (smb_context, 1); +#endif + + if (!smbc_init_context (smb_context)) + { + g_vfs_job_failed (G_VFS_JOB (job), + G_IO_ERROR, G_IO_ERROR_FAILED, + _("Internal Error (%s)"), "Failed to initialize smb context"); + smbc_free_context (smb_context, FALSE); + return; + } + + op_backend->smb_context = smb_context; + + /* Set the mountspec according to original uri, no matter whether user changes + credentials during mount loop. Nautilus and other gio clients depend + on correct mountspec, setting it to real (different) credentials would + lead to G_IO_ERROR_NOT_MOUNTED errors + */ + + /* Translators: This is "<sharename> on <servername>" and is used as name for an SMB share */ + display_name = g_strdup_printf (_("%s on %s"), op_backend->share, op_backend->server); + g_vfs_backend_set_display_name (backend, display_name); + g_free (display_name); + g_vfs_backend_set_icon_name (backend, "folder-remote"); + + smb_mount_spec = g_mount_spec_new ("smb-share"); + g_mount_spec_set (smb_mount_spec, "share", op_backend->share); + g_mount_spec_set (smb_mount_spec, "server", op_backend->server); + if (op_backend->user) + g_mount_spec_set (smb_mount_spec, "user", op_backend->user); + if (op_backend->domain) + g_mount_spec_set (smb_mount_spec, "domain", op_backend->domain); + + g_vfs_backend_set_mount_spec (backend, smb_mount_spec); + g_mount_spec_unref (smb_mount_spec); + + uri = create_smb_uri (op_backend->server, op_backend->share, NULL); + + + /* Samba mount loop */ + op_backend->mount_source = mount_source; + op_backend->mount_try = 0; + op_backend->password_save = G_PASSWORD_SAVE_NEVER; + + do + { + op_backend->mount_try_again = FALSE; + op_backend->mount_cancelled = FALSE; + + smbc_stat = smbc_getFunctionStat (smb_context); + res = smbc_stat (smb_context, uri, &st); + + if (res == 0 || op_backend->mount_cancelled || + (errno != EACCES && errno != EPERM)) + break; + + /* The first round is Kerberos-only. Only if this fails do we enable + * NTLMSSP fallback (turning off anonymous fallback, which we've + * already tried and failed with). + */ + if (op_backend->mount_try == 0) + { + smbc_setOptionFallbackAfterKerberos (op_backend->smb_context, 1); + smbc_setOptionNoAutoAnonymousLogin (op_backend->smb_context, 1); + } + op_backend->mount_try ++; + } + while (op_backend->mount_try_again); + + g_free (uri); + + op_backend->mount_source = NULL; + + if (res != 0) + { + /* TODO: Error from errno? */ + op_backend->mount_source = NULL; + + if (op_backend->mount_cancelled) + g_vfs_job_failed (G_VFS_JOB (job), + G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED, + _("Password dialog cancelled")); + else + g_vfs_job_failed (G_VFS_JOB (job), + G_IO_ERROR, G_IO_ERROR_FAILED, + /* translators: We tried to mount a windows (samba) share, but failed */ + _("Failed to mount Windows share")); + + return; + } + + /* Mount was successful */ + + g_vfs_keyring_save_password (op_backend->last_user, + op_backend->server, + op_backend->last_domain, + "smb", + NULL, + NULL, + 0, + op_backend->last_password, + op_backend->password_save); + + g_vfs_job_succeeded (G_VFS_JOB (job)); +} + +static gboolean +try_mount (GVfsBackend *backend, + GVfsJobMount *job, + GMountSpec *mount_spec, + GMountSource *mount_source, + gboolean is_automount) +{ + GVfsBackendSmb *op_backend = G_VFS_BACKEND_SMB (backend); + const char *server, *share, *user, *domain; + + server = g_mount_spec_get (mount_spec, "server"); + share = g_mount_spec_get (mount_spec, "share"); + + if (server == NULL || share == NULL) + { + g_vfs_job_failed (G_VFS_JOB (job), + G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, + _("Invalid mount spec")); + return TRUE; + } + + user = g_mount_spec_get (mount_spec, "user"); + domain = g_mount_spec_get (mount_spec, "domain"); + + op_backend->server = g_strdup (server); + op_backend->share = g_strdup (share); + op_backend->user = g_strdup (user); + op_backend->domain = g_strdup (domain); + + return FALSE; +} + +static void +do_open_for_read (GVfsBackend *backend, + GVfsJobOpenForRead *job, + const char *filename) +{ + GVfsBackendSmb *op_backend = G_VFS_BACKEND_SMB (backend); + char *uri; + SMBCFILE *file; + struct stat st; + smbc_open_fn smbc_open; + smbc_stat_fn smbc_stat; + int res; + int olderr; + + + uri = create_smb_uri (op_backend->server, op_backend->share, filename); + smbc_open = smbc_getFunctionOpen (op_backend->smb_context); + file = smbc_open (op_backend->smb_context, uri, O_RDONLY, 0); + + if (file == NULL) + { + olderr = errno; + smbc_stat = smbc_getFunctionStat (op_backend->smb_context); + res = smbc_stat (op_backend->smb_context, uri, &st); + g_free (uri); + if ((res == 0) && (S_ISDIR (st.st_mode))) + g_vfs_job_failed (G_VFS_JOB (job), + G_IO_ERROR, G_IO_ERROR_IS_DIRECTORY, + _("Can't open directory")); + else + g_vfs_job_failed_from_errno (G_VFS_JOB (job), olderr); + } + else + { + + g_vfs_job_open_for_read_set_can_seek (job, TRUE); + g_vfs_job_open_for_read_set_handle (job, file); + g_vfs_job_succeeded (G_VFS_JOB (job)); + } +} + +static void +do_read (GVfsBackend *backend, + GVfsJobRead *job, + GVfsBackendHandle handle, + char *buffer, + gsize bytes_requested) +{ + GVfsBackendSmb *op_backend = G_VFS_BACKEND_SMB (backend); + ssize_t res; + smbc_read_fn smbc_read; + + /* For some reason requests of 65536 bytes broke for me (returned 0) + * Maybe some smb protocol limit + */ + if (bytes_requested > 65535) + bytes_requested = 65535; + + smbc_read = smbc_getFunctionRead (op_backend->smb_context); + res = smbc_read (op_backend->smb_context, (SMBCFILE *)handle, buffer, bytes_requested); + + if (res == -1) + g_vfs_job_failed_from_errno (G_VFS_JOB (job), errno); + else + { + g_vfs_job_read_set_size (job, res); + g_vfs_job_succeeded (G_VFS_JOB (job)); + + } +} + +static void +do_seek_on_read (GVfsBackend *backend, + GVfsJobSeekRead *job, + GVfsBackendHandle handle, + goffset offset, + GSeekType type) +{ + GVfsBackendSmb *op_backend = G_VFS_BACKEND_SMB (backend); + int whence; + off_t res; + smbc_lseek_fn smbc_lseek; + + switch (type) + { + case G_SEEK_SET: + whence = SEEK_SET; + break; + case G_SEEK_CUR: + whence = SEEK_CUR; + break; + case G_SEEK_END: + whence = SEEK_END; + break; + default: + g_vfs_job_failed (G_VFS_JOB (job), + G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, + _("Unsupported seek type")); + return; + } + + smbc_lseek = smbc_getFunctionLseek (op_backend->smb_context); + res = smbc_lseek (op_backend->smb_context, (SMBCFILE *)handle, offset, whence); + + if (res == (off_t)-1) + g_vfs_job_failed_from_errno (G_VFS_JOB (job), errno); + else + { + g_vfs_job_seek_read_set_offset (job, res); + g_vfs_job_succeeded (G_VFS_JOB (job)); + } + + return; +} + +static void +do_query_info_on_read (GVfsBackend *backend, + GVfsJobQueryInfoRead *job, + GVfsBackendHandle handle, + GFileInfo *info, + GFileAttributeMatcher *matcher) +{ + GVfsBackendSmb *op_backend = G_VFS_BACKEND_SMB (backend); + struct stat st = {0}; + int res, saved_errno; + smbc_fstat_fn smbc_fstat; + + smbc_fstat = smbc_getFunctionFstat (op_backend->smb_context); + res = smbc_fstat (op_backend->smb_context, (SMBCFILE *)handle, &st); + saved_errno = errno; + + if (res == 0) + { + set_info_from_stat (op_backend, info, &st, NULL, matcher); + + g_vfs_job_succeeded (G_VFS_JOB (job)); + } + else + g_vfs_job_failed_from_errno (G_VFS_JOB (job), saved_errno); + +} + +static void +do_close_read (GVfsBackend *backend, + GVfsJobCloseRead *job, + GVfsBackendHandle handle) +{ + GVfsBackendSmb *op_backend = G_VFS_BACKEND_SMB (backend); + ssize_t res; + smbc_close_fn smbc_close; + + smbc_close = smbc_getFunctionClose (op_backend->smb_context); + res = smbc_close (op_backend->smb_context, (SMBCFILE *)handle); + if (res == -1) + g_vfs_job_failed_from_errno (G_VFS_JOB (job), errno); + else + g_vfs_job_succeeded (G_VFS_JOB (job)); +} + +typedef struct { + SMBCFILE *file; + char *uri; + char *tmp_uri; + char *backup_uri; +} SmbWriteHandle; + +static void +smb_write_handle_free (SmbWriteHandle *handle) +{ + g_free (handle->uri); + g_free (handle->tmp_uri); + g_free (handle->backup_uri); + g_free (handle); +} + +static void +do_create (GVfsBackend *backend, + GVfsJobOpenForWrite *job, + const char *filename, + GFileCreateFlags flags) +{ + GVfsBackendSmb *op_backend = G_VFS_BACKEND_SMB (backend); + char *uri; + SMBCFILE *file; + SmbWriteHandle *handle; + smbc_open_fn smbc_open; + + uri = create_smb_uri (op_backend->server, op_backend->share, filename); + smbc_open = smbc_getFunctionOpen (op_backend->smb_context); + file = smbc_open (op_backend->smb_context, uri, + O_CREAT|O_WRONLY|O_EXCL, 0666); + g_free (uri); + + if (file == NULL) + g_vfs_job_failed_from_errno (G_VFS_JOB (job), errno); + else + { + handle = g_new0 (SmbWriteHandle, 1); + handle->file = file; + + g_vfs_job_open_for_write_set_can_seek (job, TRUE); + g_vfs_job_open_for_write_set_handle (job, handle); + g_vfs_job_succeeded (G_VFS_JOB (job)); + } +} + +static void +do_append_to (GVfsBackend *backend, + GVfsJobOpenForWrite *job, + const char *filename, + GFileCreateFlags flags) +{ + GVfsBackendSmb *op_backend = G_VFS_BACKEND_SMB (backend); + char *uri; + SMBCFILE *file; + SmbWriteHandle *handle; + off_t initial_offset; + smbc_open_fn smbc_open; + smbc_lseek_fn smbc_lseek; + + uri = create_smb_uri (op_backend->server, op_backend->share, filename); + smbc_open = smbc_getFunctionOpen (op_backend->smb_context); + file = smbc_open (op_backend->smb_context, uri, + O_CREAT|O_WRONLY|O_APPEND, 0666); + g_free (uri); + + if (file == NULL) + g_vfs_job_failed_from_errno (G_VFS_JOB (job), errno); + else + { + handle = g_new0 (SmbWriteHandle, 1); + handle->file = file; + + smbc_lseek = smbc_getFunctionLseek (op_backend->smb_context); + initial_offset = smbc_lseek (op_backend->smb_context, file, + 0, SEEK_CUR); + if (initial_offset == (off_t) -1) + g_vfs_job_open_for_write_set_can_seek (job, FALSE); + else + { + g_vfs_job_open_for_write_set_initial_offset (job, initial_offset); + g_vfs_job_open_for_write_set_can_seek (job, TRUE); + } + g_vfs_job_open_for_write_set_handle (job, handle); + g_vfs_job_succeeded (G_VFS_JOB (job)); + } +} + + +static void +random_chars (char *str, int len) +{ + int i; + const char chars[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; + + for (i = 0; i < len; i++) + str[i] = chars[g_random_int_range (0, strlen(chars))]; +} + +static char * +get_dir_from_uri (const char *uri) +{ + const char *prefix_end; + + prefix_end = uri + strlen (uri); + + /* Skip slashes at end */ + while (prefix_end > uri && + *(prefix_end - 1) == '/') + prefix_end--; + + /* Skip to next slash */ + while (prefix_end > uri && + *(prefix_end - 1) != '/') + prefix_end--; + + return g_strndup (uri, prefix_end - uri); +} + +static SMBCFILE * +open_tmpfile (GVfsBackendSmb *backend, + const char *uri, + char **tmp_uri_out) +{ + char *dir_uri, *tmp_uri; + char filename[] = "~gvfXXXX.tmp"; + SMBCFILE *file; + smbc_open_fn smbc_open; + + dir_uri = get_dir_from_uri (uri); + + do { + random_chars (filename + 4, 4); + tmp_uri = g_strconcat (dir_uri, filename, NULL); + + smbc_open = smbc_getFunctionOpen (backend->smb_context); + file = smbc_open (backend->smb_context, tmp_uri, + O_CREAT|O_WRONLY|O_EXCL, 0666); + } while (file == NULL && errno == EEXIST); + + g_free (dir_uri); + + if (file) + { + *tmp_uri_out = tmp_uri; + return file; + } + else + { + g_free (tmp_uri); + return NULL; + } +} + +static gboolean +copy_file (GVfsBackendSmb *backend, + GVfsJob *job, + const char *from_uri, + const char *to_uri) +{ + SMBCFILE *from_file, *to_file; + char buffer[4096]; + size_t buffer_size; + ssize_t res; + char *p; + gboolean succeeded; + smbc_open_fn smbc_open; + smbc_read_fn smbc_read; + smbc_write_fn smbc_write; + smbc_close_fn smbc_close; + + + from_file = NULL; + to_file = NULL; + + succeeded = FALSE; + + smbc_open = smbc_getFunctionOpen (backend->smb_context); + smbc_read = smbc_getFunctionRead (backend->smb_context); + smbc_write = smbc_getFunctionWrite (backend->smb_context); + smbc_close = smbc_getFunctionClose (backend->smb_context); + + from_file = smbc_open (backend->smb_context, from_uri, + O_RDONLY, 0666); + if (from_file == NULL || g_vfs_job_is_cancelled (job)) + goto out; + + to_file = smbc_open (backend->smb_context, to_uri, + O_CREAT|O_WRONLY|O_TRUNC, 0666); + + if (from_file == NULL || g_vfs_job_is_cancelled (job)) + goto out; + + while (1) + { + + res = smbc_read (backend->smb_context, from_file, + buffer, sizeof(buffer)); + if (res < 0 || g_vfs_job_is_cancelled (job)) + goto out; + if (res == 0) + break; /* Succeeded */ + + buffer_size = res; + p = buffer; + while (buffer_size > 0) + { + res = smbc_write (backend->smb_context, to_file, + p, buffer_size); + if (res < 0 || g_vfs_job_is_cancelled (job)) + goto out; + buffer_size -= res; + p += res; + } + } + succeeded = TRUE; + + out: + if (to_file) + smbc_close (backend->smb_context, to_file); + if (from_file) + smbc_close (backend->smb_context, from_file); + return succeeded; +} + +static char * +create_etag (struct stat *statbuf) +{ + return g_strdup_printf ("%lu", (long unsigned int)statbuf->st_mtime); +} + +static void +do_replace (GVfsBackend *backend, + GVfsJobOpenForWrite *job, + const char *filename, + const char *etag, + gboolean make_backup, + GFileCreateFlags flags) +{ + GVfsBackendSmb *op_backend = G_VFS_BACKEND_SMB (backend); + struct stat original_stat; + int res; + char *uri, *tmp_uri, *backup_uri, *current_etag; + SMBCFILE *file; + GError *error = NULL; + SmbWriteHandle *handle; + smbc_open_fn smbc_open; + smbc_stat_fn smbc_stat; + + uri = create_smb_uri (op_backend->server, op_backend->share, filename); + tmp_uri = NULL; + if (make_backup) + backup_uri = g_strconcat (uri, "~", NULL); + else + backup_uri = NULL; + + smbc_open = smbc_getFunctionOpen (op_backend->smb_context); + smbc_stat = smbc_getFunctionStat (op_backend->smb_context); + + file = smbc_open (op_backend->smb_context, uri, + O_CREAT|O_WRONLY|O_EXCL, 0); + if (file == NULL && errno != EEXIST) + { + int errsv = errno; + + g_set_error_literal (&error, G_IO_ERROR, + g_io_error_from_errno (errsv), + g_strerror (errsv)); + goto error; + } + else if (file == NULL && errno == EEXIST) + { + if (etag != NULL) + { + res = smbc_stat (op_backend->smb_context, uri, &original_stat); + + if (res == 0) + { + current_etag = create_etag (&original_stat); + if (strcmp (etag, current_etag) != 0) + { + g_free (current_etag); + g_set_error_literal (&error, + G_IO_ERROR, + G_IO_ERROR_WRONG_ETAG, + _("The file was externally modified")); + goto error; + } + g_free (current_etag); + } + } + + /* Backup strategy: + * + * By default we: + * 1) save to a tmp file (that doesn't exist already) + * 2) rename orig file to backup file + * (or delete it if no backup) + * 3) rename tmp file to orig file + * + * However, this can fail if we can't write to the directory. + * In that case we just truncate the file, after having + * copied directly to the backup filename. + */ + + file = open_tmpfile (op_backend, uri, &tmp_uri); + if (file == NULL) + { + if (make_backup) + { + if (!copy_file (op_backend, G_VFS_JOB (job), uri, backup_uri)) + { + if (g_vfs_job_is_cancelled (G_VFS_JOB (job))) + g_set_error_literal (&error, + G_IO_ERROR, + G_IO_ERROR_CANCELLED, + _("Operation was cancelled")); + else + g_set_error_literal (&error, + G_IO_ERROR, + G_IO_ERROR_CANT_CREATE_BACKUP, + _("Backup file creation failed")); + goto error; + } + g_free (backup_uri); + backup_uri = NULL; + } + + file = smbc_open (op_backend->smb_context, uri, + O_CREAT|O_WRONLY|O_TRUNC, 0); + if (file == NULL) + { + int errsv = errno; + + g_set_error_literal (&error, G_IO_ERROR, + g_io_error_from_errno (errsv), + g_strerror (errsv)); + goto error; + } + } + } + else + { + /* Doesn't exist. Just write away */ + g_free (backup_uri); + backup_uri = NULL; + } + + handle = g_new (SmbWriteHandle, 1); + handle->file = file; + handle->uri = uri; + handle->tmp_uri = tmp_uri; + handle->backup_uri = backup_uri; + + g_vfs_job_open_for_write_set_can_seek (job, TRUE); + g_vfs_job_open_for_write_set_handle (job, handle); + g_vfs_job_succeeded (G_VFS_JOB (job)); + + return; + + error: + g_vfs_job_failed_from_error (G_VFS_JOB (job), error); + g_error_free (error); + g_free (backup_uri); + g_free (tmp_uri); + g_free (uri); +} + + +static void +do_write (GVfsBackend *backend, + GVfsJobWrite *job, + GVfsBackendHandle _handle, + char *buffer, + gsize buffer_size) +{ + GVfsBackendSmb *op_backend = G_VFS_BACKEND_SMB (backend); + SmbWriteHandle *handle = _handle; + ssize_t res; + smbc_write_fn smbc_write; + + smbc_write = smbc_getFunctionWrite (op_backend->smb_context); + res = smbc_write (op_backend->smb_context, handle->file, + buffer, buffer_size); + if (res == -1) + g_vfs_job_failed_from_errno (G_VFS_JOB (job), errno); + else + { + g_vfs_job_write_set_written_size (job, res); + g_vfs_job_succeeded (G_VFS_JOB (job)); + } +} + +static void +do_seek_on_write (GVfsBackend *backend, + GVfsJobSeekWrite *job, + GVfsBackendHandle _handle, + goffset offset, + GSeekType type) +{ + GVfsBackendSmb *op_backend = G_VFS_BACKEND_SMB (backend); + SmbWriteHandle *handle = _handle; + int whence; + off_t res; + smbc_lseek_fn smbc_lseek; + + switch (type) + { + case G_SEEK_SET: + whence = SEEK_SET; + break; + case G_SEEK_CUR: + whence = SEEK_CUR; + break; + case G_SEEK_END: + whence = SEEK_END; + break; + default: + g_vfs_job_failed (G_VFS_JOB (job), + G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, + _("Unsupported seek type")); + return; + } + + smbc_lseek = smbc_getFunctionLseek (op_backend->smb_context); + res = smbc_lseek (op_backend->smb_context, handle->file, offset, whence); + + if (res == (off_t)-1) + g_vfs_job_failed_from_errno (G_VFS_JOB (job), errno); + else + { + g_vfs_job_seek_write_set_offset (job, res); + g_vfs_job_succeeded (G_VFS_JOB (job)); + } + + return; +} + +static void +do_query_info_on_write (GVfsBackend *backend, + GVfsJobQueryInfoWrite *job, + GVfsBackendHandle _handle, + GFileInfo *info, + GFileAttributeMatcher *matcher) +{ + GVfsBackendSmb *op_backend = G_VFS_BACKEND_SMB (backend); + struct stat st = {0}; + SmbWriteHandle *handle = _handle; + int res, saved_errno; + smbc_fstat_fn smbc_fstat; + + smbc_fstat = smbc_getFunctionFstat (op_backend->smb_context); + res = smbc_fstat (op_backend->smb_context, handle->file, &st); + saved_errno = errno; + + if (res == 0) + { + set_info_from_stat (op_backend, info, &st, NULL, matcher); + + g_vfs_job_succeeded (G_VFS_JOB (job)); + } + else + g_vfs_job_failed_from_errno (G_VFS_JOB (job), saved_errno); + +} + +static void +do_close_write (GVfsBackend *backend, + GVfsJobCloseWrite *job, + GVfsBackendHandle _handle) +{ + GVfsBackendSmb *op_backend = G_VFS_BACKEND_SMB (backend); + SmbWriteHandle *handle = _handle; + struct stat stat_at_close; + int stat_res; + ssize_t res; + smbc_fstat_fn smbc_fstat; + smbc_close_fn smbc_close; + smbc_unlink_fn smbc_unlink; + smbc_rename_fn smbc_rename; + + smbc_fstat = smbc_getFunctionFstat (op_backend->smb_context); + smbc_close = smbc_getFunctionClose (op_backend->smb_context); + smbc_unlink = smbc_getFunctionUnlink (op_backend->smb_context); + smbc_rename = smbc_getFunctionRename (op_backend->smb_context); + + stat_res = smbc_fstat (op_backend->smb_context, handle->file, &stat_at_close); + + res = smbc_close (op_backend->smb_context, handle->file); + + if (res == -1) + { + g_vfs_job_failed_from_errno (G_VFS_JOB (job), errno); + + if (handle->tmp_uri) + smbc_unlink (op_backend->smb_context, handle->tmp_uri); + goto out; + } + + if (handle->tmp_uri) + { + if (handle->backup_uri) + { + res = smbc_rename (op_backend->smb_context, handle->uri, + op_backend->smb_context, handle->backup_uri); + if (res == -1) + { + int errsv = errno; + + smbc_unlink (op_backend->smb_context, handle->tmp_uri); + g_vfs_job_failed (G_VFS_JOB (job), + G_IO_ERROR, G_IO_ERROR_CANT_CREATE_BACKUP, + _("Backup file creation failed: %s"), g_strerror (errsv)); + goto out; + } + } + else + smbc_unlink (op_backend->smb_context, handle->uri); + + res = smbc_rename (op_backend->smb_context, handle->tmp_uri, + op_backend->smb_context, handle->uri); + if (res == -1) + { + smbc_unlink (op_backend->smb_context, handle->tmp_uri); + g_vfs_job_failed_from_errno (G_VFS_JOB (job), errno); + goto out; + } + } + + if (stat_res == 0) + { + char *etag; + etag = create_etag (&stat_at_close); + g_vfs_job_close_write_set_etag (job, etag); + g_free (etag); + } + + g_vfs_job_succeeded (G_VFS_JOB (job)); + + out: + smb_write_handle_free (handle); +} + +static void +set_info_from_stat (GVfsBackendSmb *backend, + GFileInfo *info, + struct stat *statbuf, + const char *basename, + GFileAttributeMatcher *matcher) +{ + GFileType file_type; + GTimeVal t; + GIcon *icon; + char *content_type; + char *display_name; + + if (basename) + { + g_file_info_set_name (info, basename); + if (*basename == '.') + g_file_info_set_is_hidden (info, TRUE); + } + + + if (basename != NULL && + g_file_attribute_matcher_matches (matcher, + G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME)) + { + if (strcmp (basename, "/") == 0) + display_name = g_strdup_printf (_("%s on %s"), backend->share, backend->server); + else + display_name = g_filename_display_name (basename); + + 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 (info, display_name); + g_free (display_name); + } + + if (basename != NULL && + g_file_attribute_matcher_matches (matcher, + G_FILE_ATTRIBUTE_STANDARD_EDIT_NAME)) + { + char *edit_name = g_filename_display_name (basename); + g_file_info_set_edit_name (info, edit_name); + g_free (edit_name); + } + + + file_type = G_FILE_TYPE_UNKNOWN; + + if (S_ISREG (statbuf->st_mode)) + file_type = G_FILE_TYPE_REGULAR; + else if (S_ISDIR (statbuf->st_mode)) + file_type = G_FILE_TYPE_DIRECTORY; + else if (S_ISCHR (statbuf->st_mode) || + S_ISBLK (statbuf->st_mode) || + S_ISFIFO (statbuf->st_mode) +#ifdef S_ISSOCK + || S_ISSOCK (statbuf->st_mode) +#endif + ) + file_type = G_FILE_TYPE_SPECIAL; + else if (S_ISLNK (statbuf->st_mode)) + file_type = G_FILE_TYPE_SYMBOLIC_LINK; + + g_file_info_set_file_type (info, file_type); + g_file_info_set_size (info, statbuf->st_size); + + t.tv_sec = statbuf->st_mtime; +#if defined (HAVE_STRUCT_STAT_ST_MTIMENSEC) + t.tv_usec = statbuf->st_mtimensec / 1000; +#elif defined (HAVE_STRUCT_STAT_ST_MTIM_TV_NSEC) + t.tv_usec = statbuf->st_mtim.tv_nsec / 1000; +#else + t.tv_usec = 0; +#endif + g_file_info_set_modification_time (info, &t); + + + if (g_file_attribute_matcher_matches (matcher, + G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE) || + g_file_attribute_matcher_matches (matcher, + G_FILE_ATTRIBUTE_STANDARD_ICON)) + { + icon = NULL; + if (S_ISDIR(statbuf->st_mode)) + { + content_type = g_strdup ("inode/directory"); + if (strcmp (basename, "/") == 0) + icon = g_themed_icon_new ("folder-remote"); + else + icon = g_themed_icon_new ("folder"); + } + else + { + content_type = g_content_type_guess (basename, NULL, 0, NULL); + if (content_type) + icon = g_content_type_get_icon (content_type); + } + + if (content_type) + { + g_file_info_set_content_type (info, content_type); + g_free (content_type); + } + + if (icon == NULL) + icon = g_themed_icon_new ("text-x-generic"); + + g_file_info_set_icon (info, icon); + g_object_unref (icon); + } + + /* Don't trust n_link, uid, gid, etc returned from libsmb, its just made up. + These are ok though: */ + + g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_DEVICE, statbuf->st_dev); + g_file_info_set_attribute_uint64 (info, G_FILE_ATTRIBUTE_UNIX_INODE, statbuf->st_ino); + + /* If file is dos-readonly, libsmbclient doesn't set S_IWUSR, we use this to + trigger ACCESS_WRITE = FALSE: */ + if (!(statbuf->st_mode & S_IWUSR)) + g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE, FALSE); + + g_file_info_set_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_ACCESS, statbuf->st_atime); +#if defined (HAVE_STRUCT_STAT_ST_ATIMENSEC) + g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_TIME_ACCESS_USEC, statbuf->st_atimensec / 1000); +#elif defined (HAVE_STRUCT_STAT_ST_ATIM_TV_NSEC) + g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_TIME_ACCESS_USEC, statbuf->st_atim.tv_nsec / 1000); +#endif + g_file_info_set_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_CHANGED, statbuf->st_ctime); +#if defined (HAVE_STRUCT_STAT_ST_CTIMENSEC) + g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_TIME_CHANGED_USEC, statbuf->st_ctimensec / 1000); +#elif defined (HAVE_STRUCT_STAT_ST_CTIM_TV_NSEC) + g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_TIME_CHANGED_USEC, statbuf->st_ctim.tv_nsec / 1000); +#endif + + /* Libsmb sets the X bit on files to indicate some special things: */ + if ((statbuf->st_mode & S_IFDIR) == 0) { + + if (statbuf->st_mode & S_IXOTH) + g_file_info_set_is_hidden (info, TRUE); + + if (statbuf->st_mode & S_IXUSR) + g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_DOS_IS_ARCHIVE, TRUE); + + if (statbuf->st_mode & S_IXGRP) + g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_DOS_IS_SYSTEM, TRUE); + } + + if (g_file_attribute_matcher_matches (matcher, G_FILE_ATTRIBUTE_ETAG_VALUE)) + { + char *etag = create_etag (statbuf); + g_file_info_set_attribute_string (info, G_FILE_ATTRIBUTE_ETAG_VALUE, etag); + g_free (etag); + } +} + +static void +do_query_info (GVfsBackend *backend, + GVfsJobQueryInfo *job, + const char *filename, + GFileQueryInfoFlags flags, + GFileInfo *info, + GFileAttributeMatcher *matcher) +{ + GVfsBackendSmb *op_backend = G_VFS_BACKEND_SMB (backend); + struct stat st = {0}; + char *uri; + int res, saved_errno; + char *basename; + smbc_stat_fn smbc_stat; + + uri = create_smb_uri (op_backend->server, op_backend->share, filename); + smbc_stat = smbc_getFunctionStat (op_backend->smb_context); + res = smbc_stat (op_backend->smb_context, uri, &st); + saved_errno = errno; + g_free (uri); + + if (res == 0) + { + basename = g_path_get_basename (filename); + set_info_from_stat (op_backend, info, &st, basename, matcher); + g_free (basename); + + g_vfs_job_succeeded (G_VFS_JOB (job)); + } + else + g_vfs_job_failed_from_errno (G_VFS_JOB (job), saved_errno); + +} + +static void +do_query_fs_info (GVfsBackend *backend, + GVfsJobQueryFsInfo *job, + const char *filename, + GFileInfo *info, + GFileAttributeMatcher *attribute_matcher) +{ + g_file_info_set_attribute_string (info, G_FILE_ATTRIBUTE_FILESYSTEM_TYPE, "cifs"); + + g_vfs_job_succeeded (G_VFS_JOB (job)); +} + + +static gboolean +try_query_settable_attributes (GVfsBackend *backend, + GVfsJobQueryAttributes *job, + const char *filename) +{ + GFileAttributeInfoList *list; + + list = g_file_attribute_info_list_new (); + + /* TODO: Add all settable attributes here -- bug #559586 */ + /* TODO: xattrs support? */ + + 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); + +#if 0 +/* FIXME: disabled; despite chmod is supported, it makes no sense on samba shares and + libsmbclient lacks proper API to read unix file modes. + The struct stat->st_mode member is used for special Windows attributes. */ + 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); +#endif + + 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 +do_set_attribute (GVfsBackend *backend, + GVfsJobSetAttribute *job, + const char *filename, + const char *attribute, + GFileAttributeType type, + gpointer value_p, + GFileQueryInfoFlags flags) +{ + GVfsBackendSmb *op_backend; + char *uri; + int res, errsv; + struct timeval tbuf[2]; + smbc_utimes_fn smbc_utimes; +#if 0 + smbc_chmod_fn smbc_chmod; +#endif + + + op_backend = G_VFS_BACKEND_SMB (backend); + + if (strcmp (attribute, G_FILE_ATTRIBUTE_TIME_MODIFIED) != 0 +#if 0 + && strcmp (attribute, G_FILE_ATTRIBUTE_UNIX_MODE) != 0 +#endif + ) + { + g_vfs_job_failed (G_VFS_JOB (job), + G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, + _("Operation unsupported")); + return; + } + + uri = create_smb_uri (op_backend->server, op_backend->share, filename); + res = -1; + + if (strcmp (attribute, G_FILE_ATTRIBUTE_TIME_MODIFIED) == 0) + { + smbc_utimes = smbc_getFunctionUtimes (op_backend->smb_context); + tbuf[1].tv_sec = (*(guint64 *)value_p); /* mtime */ + tbuf[1].tv_usec = 0; + /* atime = mtime (atimes are usually disabled on desktop systems) */ + tbuf[0].tv_sec = tbuf[1].tv_sec; + tbuf[0].tv_usec = 0; + res = smbc_utimes (op_backend->smb_context, uri, &tbuf[0]); + } +#if 0 + else + if (strcmp (attribute, G_FILE_ATTRIBUTE_UNIX_MODE) == 0) + { + smbc_chmod = smbc_getFunctionChmod (op_backend->smb_context); + res = smbc_chmod (op_backend->smb_context, uri, (*(guint32 *)value_p) & 0777); + } +#endif + + errsv = errno; + g_free (uri); + + if (res != 0) + g_vfs_job_failed_from_errno (G_VFS_JOB (job), errsv); + else + g_vfs_job_succeeded (G_VFS_JOB (job)); +} + +static void +do_enumerate (GVfsBackend *backend, + GVfsJobEnumerate *job, + const char *filename, + GFileAttributeMatcher *matcher, + GFileQueryInfoFlags flags) +{ + GVfsBackendSmb *op_backend = G_VFS_BACKEND_SMB (backend); + struct stat st; + int res; + GError *error; + SMBCFILE *dir; + char dirents[1024*4]; + struct smbc_dirent *dirp; + GList *files; + GFileInfo *info; + GString *uri; + int uri_start_len; + smbc_opendir_fn smbc_opendir; + smbc_getdents_fn smbc_getdents; + smbc_stat_fn smbc_stat; + smbc_closedir_fn smbc_closedir; + + uri = create_smb_uri_string (op_backend->server, op_backend->share, filename); + + smbc_opendir = smbc_getFunctionOpendir (op_backend->smb_context); + smbc_getdents = smbc_getFunctionGetdents (op_backend->smb_context); + smbc_stat = smbc_getFunctionStat (op_backend->smb_context); + smbc_closedir = smbc_getFunctionClosedir (op_backend->smb_context); + + dir = smbc_opendir (op_backend->smb_context, uri->str); + + if (dir == NULL) + { + int errsv = errno; + + error = NULL; + g_set_error_literal (&error, G_IO_ERROR, + g_io_error_from_errno (errsv), + g_strerror (errsv)); + goto error; + } + + g_vfs_job_succeeded (G_VFS_JOB (job)); + + if (uri->str[uri->len - 1] != '/') + g_string_append_c (uri, '/'); + uri_start_len = uri->len; + + while (TRUE) + { + files = NULL; + + res = smbc_getdents (op_backend->smb_context, dir, (struct smbc_dirent *)dirents, sizeof (dirents)); + if (res <= 0) + break; + + dirp = (struct smbc_dirent *)dirents; + while (res > 0) + { + unsigned int dirlen; + + /* TODO: Only do stat if required for flags */ + + if ((dirp->smbc_type == SMBC_DIR || + dirp->smbc_type == SMBC_FILE || + dirp->smbc_type == SMBC_LINK) && + strcmp (dirp->name, ".") != 0 && + strcmp (dirp->name, "..") != 0) + { + int stat_res; + g_string_truncate (uri, uri_start_len); + g_string_append_encoded (uri, + dirp->name, + SUB_DELIM_CHARS ":@/"); + + if (matcher == NULL || + g_file_attribute_matcher_matches_only (matcher, G_FILE_ATTRIBUTE_STANDARD_NAME)) + { + info = g_file_info_new (); + g_file_info_set_name (info, dirp->name); + files = g_list_prepend (files, info); + } + else + { + stat_res = smbc_stat (op_backend->smb_context, + uri->str, &st); + if (stat_res == 0) + { + info = g_file_info_new (); + set_info_from_stat (op_backend, info, &st, dirp->name, matcher); + files = g_list_prepend (files, info); + } + } + } + + dirlen = dirp->dirlen; + dirp = (struct smbc_dirent *) (((char *)dirp) + dirlen); + res -= dirlen; + } + + if (files) + { + files = g_list_reverse (files); + g_vfs_job_enumerate_add_infos (job, files); + g_list_foreach (files, (GFunc)g_object_unref, NULL); + g_list_free (files); + } + } + + res = smbc_closedir (op_backend->smb_context, dir); + + g_vfs_job_enumerate_done (job); + + g_string_free (uri, TRUE); + return; + + error: + g_vfs_job_failed_from_error (G_VFS_JOB (job), error); + g_error_free (error); + g_string_free (uri, TRUE); +} + +static void +do_set_display_name (GVfsBackend *backend, + GVfsJobSetDisplayName *job, + const char *filename, + const char *display_name) +{ + GVfsBackendSmb *op_backend = G_VFS_BACKEND_SMB (backend); + char *from_uri, *to_uri; + char *dirname, *new_path; + int res, errsv; + smbc_rename_fn smbc_rename; + + dirname = g_path_get_dirname (filename); + + /* TODO: display name is in utf8, atm we assume libsmb uris + are in utf8, but this might not be true if the user changed + the smb.conf file. Can we check this and convert? */ + + new_path = g_build_filename (dirname, display_name, NULL); + g_free (dirname); + + from_uri = create_smb_uri (op_backend->server, op_backend->share, filename); + to_uri = create_smb_uri (op_backend->server, op_backend->share, new_path); + + smbc_rename = smbc_getFunctionRename (op_backend->smb_context); + res = smbc_rename (op_backend->smb_context, from_uri, + op_backend->smb_context, to_uri); + errsv = errno; + g_free (from_uri); + g_free (to_uri); + + if (res != 0) + g_vfs_job_failed_from_errno (G_VFS_JOB (job), errsv); + else + { + g_vfs_job_set_display_name_set_new_path (job, new_path); + g_vfs_job_succeeded (G_VFS_JOB (job)); + } + g_free (new_path); +} + +static void +do_delete (GVfsBackend *backend, + GVfsJobDelete *job, + const char *filename) +{ + GVfsBackendSmb *op_backend = G_VFS_BACKEND_SMB (backend); + struct stat statbuf; + char *uri; + int errsv, res; + smbc_stat_fn smbc_stat; + smbc_rmdir_fn smbc_rmdir; + smbc_unlink_fn smbc_unlink; + + + uri = create_smb_uri (op_backend->server, op_backend->share, filename); + + smbc_stat = smbc_getFunctionStat (op_backend->smb_context); + smbc_rmdir = smbc_getFunctionRmdir (op_backend->smb_context); + smbc_unlink = smbc_getFunctionUnlink (op_backend->smb_context); + + res = smbc_stat (op_backend->smb_context, uri, &statbuf); + if (res == -1) + { + errsv = errno; + + g_vfs_job_failed (G_VFS_JOB (job), + G_IO_ERROR, + g_io_error_from_errno (errsv), + _("Error deleting file: %s"), + g_strerror (errsv)); + g_free (uri); + return; + } + + if (S_ISDIR (statbuf.st_mode)) + res = smbc_rmdir (op_backend->smb_context, uri); + else + res = smbc_unlink (op_backend->smb_context, uri); + errsv = errno; + g_free (uri); + + if (res != 0) + g_vfs_job_failed_from_errno (G_VFS_JOB (job), errsv); + else + g_vfs_job_succeeded (G_VFS_JOB (job)); +} + +static void +do_make_directory (GVfsBackend *backend, + GVfsJobMakeDirectory *job, + const char *filename) +{ + GVfsBackendSmb *op_backend = G_VFS_BACKEND_SMB (backend); + char *uri; + int errsv, res; + smbc_mkdir_fn smbc_mkdir; + + uri = create_smb_uri (op_backend->server, op_backend->share, filename); + smbc_mkdir = smbc_getFunctionMkdir (op_backend->smb_context); + res = smbc_mkdir (op_backend->smb_context, uri, 0666); + errsv = errno; + g_free (uri); + + if (res != 0) + g_vfs_job_failed_from_errno (G_VFS_JOB (job), errsv); + else + g_vfs_job_succeeded (G_VFS_JOB (job)); +} + +static void +do_move (GVfsBackend *backend, + GVfsJobMove *job, + const char *source, + const char *destination, + GFileCopyFlags flags, + GFileProgressCallback progress_callback, + gpointer progress_callback_data) +{ + GVfsBackendSmb *op_backend = G_VFS_BACKEND_SMB (backend); + char *source_uri, *dest_uri, *backup_uri; + gboolean destination_exist, source_is_dir; + struct stat statbuf; + int res, errsv; + smbc_stat_fn smbc_stat; + smbc_rename_fn smbc_rename; + smbc_unlink_fn smbc_unlink; + + + source_uri = create_smb_uri (op_backend->server, op_backend->share, source); + + smbc_stat = smbc_getFunctionStat (op_backend->smb_context); + smbc_rename = smbc_getFunctionRename (op_backend->smb_context); + smbc_unlink = smbc_getFunctionUnlink (op_backend->smb_context); + + res = smbc_stat (op_backend->smb_context, source_uri, &statbuf); + if (res == -1) + { + errsv = errno; + + g_vfs_job_failed (G_VFS_JOB (job), + G_IO_ERROR, + g_io_error_from_errno (errsv), + _("Error moving file: %s"), + g_strerror (errsv)); + g_free (source_uri); + return; + } + else + source_is_dir = S_ISDIR (statbuf.st_mode); + + dest_uri = create_smb_uri (op_backend->server, op_backend->share, destination); + + destination_exist = FALSE; + res = smbc_stat (op_backend->smb_context, dest_uri, &statbuf); + if (res == 0) + { + destination_exist = TRUE; /* Target file exists */ + + if (flags & G_FILE_COPY_OVERWRITE) + { + /* Always fail on dirs, even with overwrite */ + if (S_ISDIR (statbuf.st_mode)) + { + g_vfs_job_failed (G_VFS_JOB (job), + G_IO_ERROR, + G_IO_ERROR_WOULD_MERGE, + _("Can't move directory over directory")); + g_free (source_uri); + g_free (dest_uri); + return; + } + } + else + { + g_vfs_job_failed (G_VFS_JOB (job), + G_IO_ERROR, + G_IO_ERROR_EXISTS, + _("Target file already exists")); + g_free (source_uri); + g_free (dest_uri); + return; + } + } + + if (flags & G_FILE_COPY_BACKUP && destination_exist) + { + backup_uri = g_strconcat (dest_uri, "~", NULL); + res = smbc_rename (op_backend->smb_context, dest_uri, + op_backend->smb_context, backup_uri); + if (res == -1) + { + g_vfs_job_failed (G_VFS_JOB (job), + G_IO_ERROR, + G_IO_ERROR_CANT_CREATE_BACKUP, + _("Backup file creation failed")); + g_free (source_uri); + g_free (dest_uri); + g_free (backup_uri); + return; + } + g_free (backup_uri); + destination_exist = FALSE; /* It did, but no more */ + } + + if (source_is_dir && destination_exist && (flags & G_FILE_COPY_OVERWRITE)) + { + /* Source is a dir, destination exists (and is not a dir, because that would have failed + earlier), and we're overwriting. Manually remove the target so we can do the rename. */ + res = smbc_unlink (op_backend->smb_context, dest_uri); + errsv = errno; + if (res == -1) + { + g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, + g_io_error_from_errno (errsv), + _("Error removing target file: %s"), + g_strerror (errsv)); + g_free (source_uri); + g_free (dest_uri); + return; + } + } + + + res = smbc_rename (op_backend->smb_context, source_uri, + op_backend->smb_context, dest_uri); + errsv = errno; + g_free (source_uri); + g_free (dest_uri); + + /* Catch moves across device boundaries */ + if (res != 0) + { + if (errsv == EXDEV || + /* Unfortunately libsmbclient doesn't correctly return EXDEV, but falls back + to EINVAL, so we try to guess when this happens: */ + (errsv == EINVAL && source_is_dir)) + g_vfs_job_failed (G_VFS_JOB (job), + G_IO_ERROR, G_IO_ERROR_WOULD_RECURSE, + _("Can't recursively move directory")); + else + g_vfs_job_failed_from_errno (G_VFS_JOB (job), errsv); + } + else + g_vfs_job_succeeded (G_VFS_JOB (job)); +} + +static void +g_vfs_backend_smb_class_init (GVfsBackendSmbClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + GVfsBackendClass *backend_class = G_VFS_BACKEND_CLASS (klass); +#ifdef HAVE_GCONF + GConfClient *gclient; +#endif + + gobject_class->finalize = g_vfs_backend_smb_finalize; + + backend_class->mount = do_mount; + 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->query_info_on_read = do_query_info_on_read; + backend_class->close_read = do_close_read; + backend_class->create = do_create; + backend_class->append_to = do_append_to; + backend_class->replace = do_replace; + backend_class->write = do_write; + backend_class->seek_on_write = do_seek_on_write; + backend_class->query_info_on_write = do_query_info_on_write; + backend_class->close_write = do_close_write; + backend_class->query_info = do_query_info; + backend_class->query_fs_info = do_query_fs_info; + backend_class->enumerate = do_enumerate; + backend_class->set_display_name = do_set_display_name; + backend_class->delete = do_delete; + backend_class->make_directory = do_make_directory; + backend_class->move = do_move; + backend_class->try_query_settable_attributes = try_query_settable_attributes; + backend_class->set_attribute = do_set_attribute; + +#ifdef HAVE_GCONF + gclient = gconf_client_get_default (); + if (gclient) + { + char *workgroup; + + workgroup = gconf_client_get_string (gclient, + PATH_GCONF_GNOME_VFS_SMB_WORKGROUP, NULL); + + if (workgroup && workgroup[0]) + default_workgroup = workgroup; + else + g_free (workgroup); + + g_object_unref (gclient); + } +#endif + +} + +void +g_vfs_smb_daemon_init (void) +{ + g_set_application_name (_("Windows Shares Filesystem Service")); +} |