1 /* GIO - GLib Input, Output and Streaming Library
2  *
3  * Copyright (C) 2006-2007 Red Hat, Inc.
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General
16  * Public License along with this library; if not, write to the
17  * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18  * Boston, MA 02110-1301, USA.
19  *
20  * Author: Alexander Larsson <alexl@redhat.com>
21  */
22 
23 #include <config.h>
24 
25 #include <sys/types.h>
26 #include <sys/stat.h>
27 #include <errno.h>
28 #include <unistd.h>
29 #include <fcntl.h>
30 #include <string.h>
31 #include <stdlib.h>
32 
33 #include <glib/gstdio.h>
34 #include <glib/gi18n.h>
35 #include <gio/gio.h>
36 
37 #include "gvfsbackendsmb.h"
38 #include "gvfsbackendsmbprivate.h"
39 #include "gvfsjobopenforread.h"
40 #include "gvfsjobread.h"
41 #include "gvfsjobseekread.h"
42 #include "gvfsjobopenforwrite.h"
43 #include "gvfsjobwrite.h"
44 #include "gvfsjobclosewrite.h"
45 #include "gvfsjobseekwrite.h"
46 #include "gvfsjobtruncate.h"
47 #include "gvfsjobsetdisplayname.h"
48 #include "gvfsjobqueryinfo.h"
49 #include "gvfsjobqueryfsinfo.h"
50 #include "gvfsjobqueryattributes.h"
51 #include "gvfsjobenumerate.h"
52 #include "gvfsjobmove.h"
53 #include "gvfsdaemonprotocol.h"
54 #include "gvfsdaemonutils.h"
55 #include "gvfsutils.h"
56 #include "gvfskeyring.h"
57 
58 #include <libsmbclient.h>
59 
60 
61 struct _GVfsBackendSmb
62 {
63   GVfsBackend parent_instance;
64 
65   char *server;
66   char *share;
67   char *user;
68   char *domain;
69   char *path;
70   char *default_workgroup;
71   int port;
72 
73   SMBCCTX *smb_context;
74 
75   char *last_user;
76   char *last_domain;
77   char *last_password;
78 
79   GMountSource *mount_source; /* Only used/set during mount */
80   int mount_try;
81   gboolean mount_try_again;
82   gboolean mount_cancelled;
83   gboolean use_anonymous;
84 
85   gboolean password_in_keyring;
86   GPasswordSave password_save;
87 };
88 
89 
90 G_DEFINE_TYPE (GVfsBackendSmb, g_vfs_backend_smb, G_VFS_TYPE_BACKEND)
91 
92 static void set_info_from_stat (GVfsBackendSmb *backend,
93 				GFileInfo *info,
94 				struct stat *statbuf,
95 				const char *basename,
96 				GFileAttributeMatcher *matcher);
97 
98 
99 static void
g_vfs_backend_smb_finalize(GObject * object)100 g_vfs_backend_smb_finalize (GObject *object)
101 {
102   GVfsBackendSmb *backend;
103 
104   backend = G_VFS_BACKEND_SMB (object);
105 
106   g_free (backend->share);
107   g_free (backend->server);
108   g_free (backend->user);
109   g_free (backend->domain);
110   g_free (backend->path);
111   g_free (backend->default_workgroup);
112 
113   if (G_OBJECT_CLASS (g_vfs_backend_smb_parent_class)->finalize)
114     (*G_OBJECT_CLASS (g_vfs_backend_smb_parent_class)->finalize) (object);
115 }
116 
117 static void
g_vfs_backend_smb_init(GVfsBackendSmb * backend)118 g_vfs_backend_smb_init (GVfsBackendSmb *backend)
119 {
120   char *workgroup;
121   GSettings *settings;
122 
123   /* Get default workgroup name */
124   settings = g_settings_new ("org.gnome.system.smb");
125 
126   workgroup = g_settings_get_string (settings, "workgroup");
127   if (workgroup && workgroup[0])
128     backend->default_workgroup = workgroup;
129   else
130     g_free (workgroup);
131 
132   g_object_unref (settings);
133 
134   g_debug ("g_vfs_backend_smb_init: default workgroup = '%s'\n", backend->default_workgroup ? backend->default_workgroup : "NULL");
135 }
136 
137 /**
138  * Authentication callback function type (method that includes context)
139  *
140  * Type for the the authentication function called by the library to
141  * obtain authentication credentals
142  *
143  * @param context   Pointer to the smb context
144  * @param srv       Server being authenticated to
145  * @param shr       Share being authenticated to
146  * @param wg        Pointer to buffer containing a "hint" for the
147  *                  workgroup to be authenticated.  Should be filled in
148  *                  with the correct workgroup if the hint is wrong.
149  * @param wglen     The size of the workgroup buffer in bytes
150  * @param un        Pointer to buffer containing a "hint" for the
151  *                  user name to be use for authentication. Should be
152  *                  filled in with the correct workgroup if the hint is
153  *                  wrong.
154  * @param unlen     The size of the username buffer in bytes
155  * @param pw        Pointer to buffer containing to which password
156  *                  copied
157  * @param pwlen     The size of the password buffer in bytes
158  *
159  */
160 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)161 auth_callback (SMBCCTX *context,
162 	       const char *server_name, const char *share_name,
163 	       char *domain_out, int domainmaxlen,
164 	       char *username_out, int unmaxlen,
165 	       char *password_out, int pwmaxlen)
166 {
167   GVfsBackendSmb *backend;
168   char *ask_password, *ask_user, *ask_domain;
169   gboolean handled, abort, anonymous = FALSE;
170 
171   backend = smbc_getOptionUserData (context);
172 
173   strncpy (password_out, "", pwmaxlen);
174 
175   if (backend->domain)
176     strncpy (domain_out, backend->domain, domainmaxlen);
177   if (backend->user)
178     strncpy (username_out, backend->user, unmaxlen);
179 
180   if (backend->mount_cancelled)
181     {
182       /*  Don't prompt for credentials, let smbclient finish the mount loop  */
183       strncpy (username_out, "ABORT", unmaxlen);
184       strncpy (password_out, "", pwmaxlen);
185       g_debug ("auth_callback - mount_cancelled\n");
186       return;
187     }
188 
189   if (backend->mount_source == NULL)
190     {
191       /* Not during mount, use last password */
192       if (backend->last_user)
193 	strncpy (username_out, backend->last_user, unmaxlen);
194       if (backend->last_domain)
195 	strncpy (domain_out, backend->last_domain, domainmaxlen);
196       if (backend->last_password)
197 	strncpy (password_out, backend->last_password, pwmaxlen);
198 
199       return;
200     }
201 
202   if (backend->mount_try == 0 &&
203       backend->user == NULL &&
204       backend->domain == NULL)
205     {
206       /* Try again if kerberos login fails */
207       backend->mount_try_again = TRUE;
208       g_debug ("auth_callback - kerberos pass\n");
209     }
210   else if (backend->mount_try == 1 &&
211            backend->user == NULL &&
212            backend->domain == NULL)
213     {
214       /* Try again if ccache login fails */
215       backend->mount_try_again = TRUE;
216       g_debug ("auth_callback - ccache pass\n");
217     }
218   else if (backend->use_anonymous)
219     {
220       /* Try again if anonymous login fails */
221       backend->use_anonymous = FALSE;
222       backend->mount_try_again = TRUE;
223       g_debug ("auth_callback - anonymous login pass\n");
224     }
225   else
226     {
227       gboolean in_keyring = FALSE;
228 
229       g_debug ("auth_callback - normal pass\n");
230 
231       if (!backend->password_in_keyring)
232         {
233 	  in_keyring = g_vfs_keyring_lookup_password (backend->user,
234 						      backend->server,
235 						      backend->domain,
236 						      "smb",
237 						      NULL,
238 						      NULL,
239 						      backend->port != -1 ? backend->port : 0,
240 						      &ask_user,
241 						      &ask_domain,
242 						      &ask_password);
243 	  backend->password_in_keyring = in_keyring;
244 
245 	  if (in_keyring)
246             g_debug ("auth_callback - reusing keyring credentials: user = '%s', domain = '%s'\n",
247                      ask_user ? ask_user : "NULL",
248                      ask_domain ? ask_domain : "NULL");
249 	}
250 
251       if (!in_keyring)
252         {
253 	  GAskPasswordFlags flags = G_ASK_PASSWORD_NEED_PASSWORD;
254 	  char *message;
255 
256 	  if (g_vfs_keyring_is_available ())
257 	    flags |= G_ASK_PASSWORD_SAVING_SUPPORTED;
258 	  if (backend->domain == NULL)
259 	    flags |= G_ASK_PASSWORD_NEED_DOMAIN;
260 	  if (backend->user == NULL)
261 	    flags |= G_ASK_PASSWORD_NEED_USERNAME;
262           if (backend->user == NULL && backend->domain == NULL)
263 	    flags |= G_ASK_PASSWORD_ANONYMOUS_SUPPORTED;
264 
265           g_debug ("auth_callback - asking for password...\n");
266 
267 	  /* translators: First %s is a share name, second is a server name */
268 	  message = g_strdup_printf (_("Password required for share %s on %s"),
269 				     share_name, server_name);
270 	  handled = g_mount_source_ask_password (backend->mount_source,
271 						 message,
272 						 username_out,
273 						 domain_out,
274 						 flags,
275 						 &abort,
276 						 &ask_password,
277 						 &ask_user,
278 						 &ask_domain,
279 						 &anonymous,
280 						 &(backend->password_save));
281 	  g_free (message);
282 	  if (!handled)
283 	    goto out;
284 
285 	  if (abort)
286 	    {
287 	      strncpy (username_out, "ABORT", unmaxlen);
288 	      strncpy (password_out, "", pwmaxlen);
289 	      backend->mount_cancelled = TRUE;
290 	      goto out;
291 	    }
292 	}
293 
294       /* Try again if this fails */
295       backend->mount_try_again = TRUE;
296 
297       if (anonymous)
298         {
299           backend->use_anonymous = TRUE;
300           backend->password_save = FALSE;
301         }
302       else
303         {
304           strncpy (password_out, ask_password, pwmaxlen);
305           if (ask_user && *ask_user)
306             strncpy (username_out, ask_user, unmaxlen);
307           if (ask_domain && *ask_domain)
308             strncpy (domain_out, ask_domain, domainmaxlen);
309         }
310 
311     out:
312       g_free (ask_password);
313       g_free (ask_user);
314       g_free (ask_domain);
315     }
316 
317   backend->last_user = g_strdup (username_out);
318   backend->last_domain = g_strdup (domain_out);
319   backend->last_password = g_strdup (password_out);
320   g_debug ("auth_callback - out: last_user = '%s', last_domain = '%s'\n",
321            backend->last_user, backend->last_domain);
322 }
323 
324 #define SUB_DELIM_CHARS  "!$&'()*+,;="
325 
326 static GString *
create_smb_uri_string(const char * server,int port,const char * share,const char * path)327 create_smb_uri_string (const char *server,
328 		       int port,
329 		       const char *share,
330 		       const char *path)
331 {
332   GString *uri;
333 
334   uri = g_string_new ("smb://");
335   if (server == NULL)
336     return uri;
337 
338   /* IPv6 server includes brackets in GMountSpec, smbclient doesn't */
339   if (gvfs_is_ipv6 (server))
340     {
341       g_string_append_uri_escaped (uri, server + 1, NULL, FALSE);
342       g_string_truncate (uri, uri->len - 3);
343     }
344   else
345     g_string_append_uri_escaped (uri, server, NULL, FALSE);
346 
347   if (port != -1)
348     g_string_append_printf (uri, ":%d", port);
349   g_string_append_c (uri, '/');
350 
351   if (share != NULL)
352     g_string_append_uri_escaped (uri, share, NULL, FALSE);
353 
354   if (path != NULL)
355     {
356       if (*path != '/')
357 	g_string_append_c (uri, '/');
358       g_string_append_uri_escaped (uri, path, SUB_DELIM_CHARS ":@/", FALSE);
359     }
360 
361   while (uri->len > 0 &&
362 	 uri->str[uri->len - 1] == '/')
363     g_string_erase (uri, uri->len - 1, 1);
364 
365   return uri;
366 }
367 
368 char *
create_smb_uri(const char * server,int port,const char * share,const char * path)369 create_smb_uri (const char *server,
370 		int port,
371 		const char *share,
372 		const char *path)
373 {
374   GString *uri;
375   uri = create_smb_uri_string (server, port, share, path);
376   return g_string_free (uri, FALSE);
377 }
378 
379 static void
do_mount(GVfsBackend * backend,GVfsJobMount * job,GMountSpec * mount_spec,GMountSource * mount_source,gboolean is_automount)380 do_mount (GVfsBackend *backend,
381 	  GVfsJobMount *job,
382 	  GMountSpec *mount_spec,
383 	  GMountSource *mount_source,
384 	  gboolean is_automount)
385 {
386   GVfsBackendSmb *op_backend = G_VFS_BACKEND_SMB (backend);
387   SMBCCTX *smb_context;
388   struct stat st;
389   char *uri;
390   int res;
391   char *display_name;
392   const char *debug;
393   int debug_val;
394   gchar *port_str;
395   GMountSpec *smb_mount_spec;
396   smbc_stat_fn smbc_stat;
397   int errsv;
398 
399   smb_context = smbc_new_context ();
400   if (smb_context == NULL)
401     {
402       g_vfs_job_failed (G_VFS_JOB (job),
403 			G_IO_ERROR, G_IO_ERROR_FAILED,
404 			_("Internal Error (%s)"), "Failed to allocate smb context");
405       return;
406     }
407   smbc_setOptionUserData (smb_context, backend);
408 
409   debug = g_getenv ("GVFS_SMB_DEBUG");
410   if (debug)
411     debug_val = atoi (debug);
412   else
413     debug_val = 0;
414 
415   smbc_setDebug (smb_context, debug_val);
416   smbc_setFunctionAuthDataWithContext (smb_context, auth_callback);
417 
418   if (op_backend->default_workgroup != NULL)
419     smbc_setWorkgroup (smb_context, op_backend->default_workgroup);
420 
421   smbc_setOptionUseKerberos (smb_context, 1);
422   smbc_setOptionFallbackAfterKerberos (smb_context,
423                                        op_backend->user != NULL);
424   smbc_setOptionNoAutoAnonymousLogin (smb_context, TRUE);
425   smbc_setOptionUseCCache (smb_context, 1);
426 
427   if (!smbc_init_context (smb_context))
428     {
429       g_vfs_job_failed (G_VFS_JOB (job),
430 			G_IO_ERROR, G_IO_ERROR_FAILED,
431 			_("Internal Error (%s)"), "Failed to initialize smb context");
432       smbc_free_context (smb_context, FALSE);
433       return;
434     }
435 
436   op_backend->smb_context = smb_context;
437 
438   /* Set the mountspec according to original uri, no matter whether user changes
439      credentials during mount loop. Nautilus and other gio clients depend
440      on correct mountspec, setting it to real (different) credentials would
441      lead to G_IO_ERROR_NOT_MOUNTED errors
442    */
443 
444   /* Translators: This is "<sharename> on <servername>" and is used as name for an SMB share */
445   display_name = g_strdup_printf (_("%s on %s"), op_backend->share, op_backend->server);
446   g_vfs_backend_set_display_name (backend, display_name);
447   g_free (display_name);
448   g_vfs_backend_set_icon_name (backend, "folder-remote");
449   g_vfs_backend_set_symbolic_icon_name (backend, "folder-remote-symbolic");
450 
451   smb_mount_spec = g_mount_spec_new ("smb-share");
452   g_mount_spec_set (smb_mount_spec, "share", op_backend->share);
453   g_mount_spec_set (smb_mount_spec, "server", op_backend->server);
454   if (op_backend->user)
455     g_mount_spec_set (smb_mount_spec, "user", op_backend->user);
456   if (op_backend->domain)
457     g_mount_spec_set (smb_mount_spec, "domain", op_backend->domain);
458   if (op_backend->port != -1)
459     {
460       port_str = g_strdup_printf ("%d", op_backend->port);
461       g_mount_spec_set (smb_mount_spec, "port", port_str);
462       g_free (port_str);
463     }
464 
465   g_vfs_backend_set_mount_spec (backend, smb_mount_spec);
466   g_mount_spec_unref (smb_mount_spec);
467 
468   /* FIXME: we're stat()-ing user-specified path here, not the root. Ideally we
469             would like to fallback to root when first mount attempt fails, though
470             it would be tough to actually say if it was an authentication failure
471             or the particular path problem. */
472   uri = create_smb_uri (op_backend->server, op_backend->port, op_backend->share, op_backend->path);
473   g_debug ("do_mount - URI = %s\n", uri);
474 
475   /*  Samba mount loop  */
476   op_backend->mount_source = mount_source;
477   op_backend->mount_try = 0;
478   op_backend->password_save = G_PASSWORD_SAVE_NEVER;
479 
480   errsv = 0;
481 
482   /* If user is not specified, first and second iteration is kerberos resp.
483    * ccache only (this is necessary in order to prevent redundant password
484    * prompts). Consequently, credentials from keyring are tried if available.
485    * Finally, user is prompted over GMountOperation if available. Anonymous is
486    * tried only if explicitely requested over GMountOperation.
487    */
488   do
489     {
490       op_backend->mount_try_again = FALSE;
491       op_backend->mount_cancelled = FALSE;
492 
493       g_debug ("do_mount - try #%d \n", op_backend->mount_try);
494 
495       smbc_stat = smbc_getFunctionStat (smb_context);
496       res = smbc_stat (smb_context, uri, &st);
497 
498       errsv = errno;
499       g_debug ("do_mount - [%s; %d] res = %d, cancelled = %d, errno = [%d] '%s' \n",
500              uri, op_backend->mount_try, res, op_backend->mount_cancelled,
501              errsv, g_strerror (errsv));
502 
503       if (res == 0)
504         break;
505 
506       if (op_backend->mount_cancelled || (errsv != EACCES && errsv != EPERM))
507         {
508           g_debug ("do_mount - (errno != EPERM && errno != EACCES), cancelled = %d, breaking\n", op_backend->mount_cancelled);
509           break;
510         }
511 
512       /* The first round is Kerberos-only.  Only if this fails do we enable
513        * NTLMSSP fallback.
514        */
515       if (op_backend->mount_try == 0 &&
516           op_backend->user == NULL)
517         {
518           g_debug ("do_mount - enabling NTLMSSP fallback\n");
519           smbc_setOptionFallbackAfterKerberos (op_backend->smb_context, 1);
520         }
521 
522       /* If the AskPassword reply requested anonymous login, enable the
523        * anonymous fallback and try again.
524        */
525       smbc_setOptionNoAutoAnonymousLogin (op_backend->smb_context,
526                                           !op_backend->use_anonymous);
527 
528       op_backend->mount_try ++;
529     }
530   while (op_backend->mount_try_again);
531 
532   g_free (uri);
533 
534   op_backend->mount_source = NULL;
535 
536   if (res != 0)
537     {
538       if (op_backend->mount_cancelled)
539         g_vfs_job_failed (G_VFS_JOB (job),
540 			  G_IO_ERROR, G_IO_ERROR_FAILED_HANDLED,
541 			  _("Password dialog cancelled"));
542       else
543         g_vfs_job_failed (G_VFS_JOB (job),
544 			  G_IO_ERROR, G_IO_ERROR_FAILED,
545 			  /* translators: We tried to mount a windows (samba) share, but failed */
546 			  _("Failed to mount Windows share: %s"), g_strerror (errsv));
547 
548       return;
549     }
550 
551   /* Mount was successful */
552   g_debug ("do_mount - login successful\n");
553 
554   g_vfs_backend_set_default_location (backend, op_backend->path);
555   g_vfs_keyring_save_password (op_backend->last_user,
556 			       op_backend->server,
557 			       op_backend->last_domain,
558 			       "smb",
559 			       NULL,
560 			       NULL,
561 			       op_backend->port != -1 ? op_backend->port : 0,
562 			       op_backend->last_password,
563 			       op_backend->password_save);
564 
565   g_vfs_job_succeeded (G_VFS_JOB (job));
566 }
567 
568 static gboolean
try_mount(GVfsBackend * backend,GVfsJobMount * job,GMountSpec * mount_spec,GMountSource * mount_source,gboolean is_automount)569 try_mount (GVfsBackend *backend,
570 	   GVfsJobMount *job,
571 	   GMountSpec *mount_spec,
572 	   GMountSource *mount_source,
573 	   gboolean is_automount)
574 {
575   GVfsBackendSmb *op_backend = G_VFS_BACKEND_SMB (backend);
576   const char *server, *share, *user, *domain, *path, *port;
577   int port_num;
578 
579   server = g_mount_spec_get (mount_spec, "server");
580   share = g_mount_spec_get (mount_spec, "share");
581 
582   if (server == NULL || share == NULL)
583     {
584       g_vfs_job_failed (G_VFS_JOB (job),
585 			G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT,
586 			_("Invalid mount spec"));
587       return TRUE;
588     }
589 
590   user = g_mount_spec_get (mount_spec, "user");
591   domain = g_mount_spec_get (mount_spec, "domain");
592   port = g_mount_spec_get (mount_spec, "port");
593   path = mount_spec->mount_prefix;
594 
595   op_backend->server = g_strdup (server);
596   op_backend->share = g_strdup (share);
597   op_backend->user = g_strdup (user);
598   op_backend->domain = g_strdup (domain);
599   op_backend->path = g_strdup (path);
600 
601   if (port && (port_num = atoi (port)))
602       op_backend->port = port_num;
603   else
604       op_backend->port = -1;
605 
606   return FALSE;
607 }
608 
609 static void
do_unmount(GVfsBackend * backend,GVfsJobUnmount * job,GMountUnmountFlags flags,GMountSource * mount_source)610 do_unmount (GVfsBackend *backend,
611 	    GVfsJobUnmount *job,
612 	    GMountUnmountFlags flags,
613 	    GMountSource *mount_source)
614 {
615   GVfsBackendSmb *op_backend = G_VFS_BACKEND_SMB (backend);
616   int res;
617 
618   if (op_backend->smb_context == NULL)
619     {
620       g_vfs_job_failed (G_VFS_JOB (job),
621 			G_IO_ERROR, G_IO_ERROR_FAILED,
622 			_("Internal Error (%s)"), "SMB context has not been initialized");
623       return;
624     }
625 
626   /* shutdown_ctx = TRUE, "all connections and files will be closed even if they are busy" */
627   res = smbc_free_context (op_backend->smb_context, TRUE);
628   op_backend->smb_context = NULL;
629   if (res != 0)
630     {
631       g_vfs_job_failed_from_errno (G_VFS_JOB (job), errno);
632       return;
633     }
634 
635   g_vfs_job_succeeded (G_VFS_JOB (job));
636 }
637 
638 static int
fixup_open_errno(int err)639 fixup_open_errno (int err)
640 {
641   /* samba has a bug (#6228) where it doesn't set errno if path resolving failed */
642   if (err == 0)
643     err = ENOTDIR;
644   return err;
645 }
646 
647 static void
do_open_for_read(GVfsBackend * backend,GVfsJobOpenForRead * job,const char * filename)648 do_open_for_read (GVfsBackend *backend,
649 		  GVfsJobOpenForRead *job,
650 		  const char *filename)
651 {
652   GVfsBackendSmb *op_backend = G_VFS_BACKEND_SMB (backend);
653   char *uri;
654   SMBCFILE *file;
655   struct stat st;
656   smbc_open_fn smbc_open;
657   smbc_stat_fn smbc_stat;
658   int res;
659   int olderr;
660 
661 
662   uri = create_smb_uri (op_backend->server, op_backend->port, op_backend->share, filename);
663   smbc_open = smbc_getFunctionOpen (op_backend->smb_context);
664   errno = 0;
665   file = smbc_open (op_backend->smb_context, uri, O_RDONLY, 0);
666 
667   if (file == NULL)
668     {
669       olderr = fixup_open_errno (errno);
670 
671       smbc_stat = smbc_getFunctionStat (op_backend->smb_context);
672       res = smbc_stat (op_backend->smb_context, uri, &st);
673       if ((res == 0) && (S_ISDIR (st.st_mode)))
674             g_vfs_job_failed (G_VFS_JOB (job),
675                               G_IO_ERROR, G_IO_ERROR_IS_DIRECTORY,
676                              _("Can’t open directory"));
677       else
678         g_vfs_job_failed_from_errno (G_VFS_JOB (job), olderr);
679   }
680   else
681     {
682 
683       g_vfs_job_open_for_read_set_can_seek (job, TRUE);
684       g_vfs_job_open_for_read_set_handle (job, file);
685       g_vfs_job_succeeded (G_VFS_JOB (job));
686     }
687   g_free (uri);
688 }
689 
690 static void
do_read(GVfsBackend * backend,GVfsJobRead * job,GVfsBackendHandle handle,char * buffer,gsize bytes_requested)691 do_read (GVfsBackend *backend,
692 	 GVfsJobRead *job,
693 	 GVfsBackendHandle handle,
694 	 char *buffer,
695 	 gsize bytes_requested)
696 {
697   GVfsBackendSmb *op_backend = G_VFS_BACKEND_SMB (backend);
698   ssize_t res;
699   smbc_read_fn smbc_read;
700 
701   smbc_read = smbc_getFunctionRead (op_backend->smb_context);
702   res = smbc_read (op_backend->smb_context, (SMBCFILE *)handle, buffer, bytes_requested);
703 
704   if (res == -1)
705     g_vfs_job_failed_from_errno (G_VFS_JOB (job), errno);
706   else
707     {
708       g_vfs_job_read_set_size (job, res);
709       g_vfs_job_succeeded (G_VFS_JOB (job));
710     }
711 }
712 
713 static void
do_seek_on_read(GVfsBackend * backend,GVfsJobSeekRead * job,GVfsBackendHandle handle,goffset offset,GSeekType type)714 do_seek_on_read (GVfsBackend *backend,
715 		 GVfsJobSeekRead *job,
716 		 GVfsBackendHandle handle,
717 		 goffset    offset,
718 		 GSeekType  type)
719 {
720   GVfsBackendSmb *op_backend = G_VFS_BACKEND_SMB (backend);
721   int whence;
722   off_t res;
723   smbc_lseek_fn smbc_lseek;
724 
725   if ((whence = gvfs_seek_type_to_lseek (type)) == -1)
726     {
727       g_vfs_job_failed (G_VFS_JOB (job),
728 			G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
729 			_("Unsupported seek type"));
730       return;
731     }
732 
733   smbc_lseek = smbc_getFunctionLseek (op_backend->smb_context);
734   res = smbc_lseek (op_backend->smb_context, (SMBCFILE *)handle, offset, whence);
735 
736   if (res == (off_t)-1)
737     g_vfs_job_failed_from_errno (G_VFS_JOB (job), errno);
738   else
739     {
740       g_vfs_job_seek_read_set_offset (job, res);
741       g_vfs_job_succeeded (G_VFS_JOB (job));
742     }
743 
744   return;
745 }
746 
747 static void
do_query_info_on_read(GVfsBackend * backend,GVfsJobQueryInfoRead * job,GVfsBackendHandle handle,GFileInfo * info,GFileAttributeMatcher * matcher)748 do_query_info_on_read (GVfsBackend *backend,
749 		       GVfsJobQueryInfoRead *job,
750 		       GVfsBackendHandle handle,
751 		       GFileInfo *info,
752 		       GFileAttributeMatcher *matcher)
753 {
754   GVfsBackendSmb *op_backend = G_VFS_BACKEND_SMB (backend);
755   struct stat st = {0};
756   int res, saved_errno;
757   smbc_fstat_fn smbc_fstat;
758 
759   smbc_fstat = smbc_getFunctionFstat (op_backend->smb_context);
760   res = smbc_fstat (op_backend->smb_context, (SMBCFILE *)handle, &st);
761   saved_errno = errno;
762 
763   if (res == 0)
764     {
765       set_info_from_stat (op_backend, info, &st, NULL, matcher);
766 
767       g_vfs_job_succeeded (G_VFS_JOB (job));
768     }
769   else
770     g_vfs_job_failed_from_errno (G_VFS_JOB (job), saved_errno);
771 
772 }
773 
774 static void
do_close_read(GVfsBackend * backend,GVfsJobCloseRead * job,GVfsBackendHandle handle)775 do_close_read (GVfsBackend *backend,
776 	       GVfsJobCloseRead *job,
777 	       GVfsBackendHandle handle)
778 {
779   GVfsBackendSmb *op_backend = G_VFS_BACKEND_SMB (backend);
780   ssize_t res;
781   smbc_close_fn smbc_close;
782 
783   smbc_close = smbc_getFunctionClose (op_backend->smb_context);
784   res = smbc_close (op_backend->smb_context, (SMBCFILE *)handle);
785   if (res == -1)
786     g_vfs_job_failed_from_errno (G_VFS_JOB (job), errno);
787   else
788     g_vfs_job_succeeded (G_VFS_JOB (job));
789 }
790 
791 typedef struct {
792   SMBCFILE *file;
793   char *uri;
794   char *tmp_uri;
795   char *backup_uri;
796 } SmbWriteHandle;
797 
798 static void
smb_write_handle_free(SmbWriteHandle * handle)799 smb_write_handle_free (SmbWriteHandle *handle)
800 {
801   g_free (handle->uri);
802   g_free (handle->tmp_uri);
803   g_free (handle->backup_uri);
804   g_free (handle);
805 }
806 
807 static void
do_create(GVfsBackend * backend,GVfsJobOpenForWrite * job,const char * filename,GFileCreateFlags flags)808 do_create (GVfsBackend *backend,
809 	   GVfsJobOpenForWrite *job,
810 	   const char *filename,
811 	   GFileCreateFlags flags)
812 {
813   GVfsBackendSmb *op_backend = G_VFS_BACKEND_SMB (backend);
814   char *uri;
815   SMBCFILE *file;
816   SmbWriteHandle *handle;
817   smbc_open_fn smbc_open;
818   int errsv;
819 
820   uri = create_smb_uri (op_backend->server, op_backend->port, op_backend->share, filename);
821   smbc_open = smbc_getFunctionOpen (op_backend->smb_context);
822   errno = 0;
823   file = smbc_open (op_backend->smb_context, uri,
824                     O_CREAT|O_RDWR|O_EXCL, 0666);
825   g_free (uri);
826 
827   if (file == NULL)
828     {
829       errsv = fixup_open_errno (errno);
830 
831       /* We guarantee EEXIST on create on existing dir */
832       if (errsv == EISDIR)
833 	errsv = EEXIST;
834       g_vfs_job_failed_from_errno (G_VFS_JOB (job), errsv);
835     }
836   else
837     {
838       handle = g_new0 (SmbWriteHandle, 1);
839       handle->file = file;
840 
841       g_vfs_job_open_for_write_set_can_seek (job, TRUE);
842       g_vfs_job_open_for_write_set_can_truncate (job, TRUE);
843       g_vfs_job_open_for_write_set_handle (job, handle);
844       g_vfs_job_succeeded (G_VFS_JOB (job));
845     }
846 }
847 
848 static void
do_append_to(GVfsBackend * backend,GVfsJobOpenForWrite * job,const char * filename,GFileCreateFlags flags)849 do_append_to (GVfsBackend *backend,
850 	      GVfsJobOpenForWrite *job,
851 	      const char *filename,
852 	      GFileCreateFlags flags)
853 {
854   GVfsBackendSmb *op_backend = G_VFS_BACKEND_SMB (backend);
855   char *uri;
856   SMBCFILE *file;
857   SmbWriteHandle *handle;
858   off_t initial_offset;
859   smbc_open_fn smbc_open;
860   smbc_lseek_fn smbc_lseek;
861 
862   uri = create_smb_uri (op_backend->server, op_backend->port, op_backend->share, filename);
863   smbc_open = smbc_getFunctionOpen (op_backend->smb_context);
864   errno = 0;
865   file = smbc_open (op_backend->smb_context, uri,
866                     O_CREAT|O_RDWR|O_APPEND, 0666);
867   g_free (uri);
868 
869   if (file == NULL)
870     g_vfs_job_failed_from_errno (G_VFS_JOB (job), fixup_open_errno (errno));
871   else
872     {
873       handle = g_new0 (SmbWriteHandle, 1);
874       handle->file = file;
875 
876       smbc_lseek = smbc_getFunctionLseek (op_backend->smb_context);
877       initial_offset = smbc_lseek (op_backend->smb_context, file,
878 						       0, SEEK_CUR);
879       if (initial_offset == (off_t) -1)
880 	g_vfs_job_open_for_write_set_can_seek (job, FALSE);
881       else
882 	{
883 	  g_vfs_job_open_for_write_set_initial_offset (job, initial_offset);
884 	  g_vfs_job_open_for_write_set_can_seek (job, TRUE);
885 	  g_vfs_job_open_for_write_set_can_truncate (job, TRUE);
886 	}
887       g_vfs_job_open_for_write_set_handle (job, handle);
888       g_vfs_job_succeeded (G_VFS_JOB (job));
889     }
890 }
891 
892 
893 static char *
get_dir_from_uri(const char * uri)894 get_dir_from_uri (const char *uri)
895 {
896   const char *prefix_end;
897 
898   prefix_end = uri + strlen (uri);
899 
900   /* Skip slashes at end */
901   while (prefix_end > uri &&
902 	 *(prefix_end - 1) == '/')
903     prefix_end--;
904 
905   /* Skip to next slash */
906   while (prefix_end > uri &&
907 	 *(prefix_end - 1) != '/')
908     prefix_end--;
909 
910   return g_strndup (uri, prefix_end - uri);
911 }
912 
913 static SMBCFILE *
open_tmpfile(GVfsBackendSmb * backend,const char * uri,char ** tmp_uri_out)914 open_tmpfile (GVfsBackendSmb *backend,
915 	      const char *uri,
916 	      char **tmp_uri_out)
917 {
918   char *dir_uri, *tmp_uri;
919   char filename[] = "~gvfXXXX.tmp";
920   SMBCFILE *file;
921   smbc_open_fn smbc_open;
922 
923   dir_uri = get_dir_from_uri (uri);
924 
925   do {
926     gvfs_randomize_string (filename + 4, 4);
927     tmp_uri = g_strconcat (dir_uri, filename, NULL);
928 
929     smbc_open = smbc_getFunctionOpen (backend->smb_context);
930     errno = 0;
931     file = smbc_open (backend->smb_context, tmp_uri,
932                       O_CREAT|O_RDWR|O_EXCL, 0666);
933   } while (file == NULL && errno == EEXIST);
934 
935   g_free (dir_uri);
936 
937   if (file)
938     {
939       *tmp_uri_out = tmp_uri;
940       return file;
941     }
942   else
943     {
944       g_free (tmp_uri);
945       return NULL;
946     }
947 }
948 
949 static gboolean
copy_file(GVfsBackendSmb * backend,GVfsJob * job,const char * from_uri,const char * to_uri)950 copy_file (GVfsBackendSmb *backend,
951 	   GVfsJob *job,
952 	   const char *from_uri,
953 	   const char *to_uri)
954 {
955   SMBCFILE *from_file, *to_file;
956   char buffer[4096];
957   size_t buffer_size;
958   ssize_t res;
959   char *p;
960   gboolean succeeded;
961   smbc_open_fn smbc_open;
962   smbc_read_fn smbc_read;
963   smbc_write_fn smbc_write;
964   smbc_close_fn smbc_close;
965 
966 
967   from_file = NULL;
968   to_file = NULL;
969 
970   succeeded = FALSE;
971 
972   smbc_open = smbc_getFunctionOpen (backend->smb_context);
973   smbc_read = smbc_getFunctionRead (backend->smb_context);
974   smbc_write = smbc_getFunctionWrite (backend->smb_context);
975   smbc_close = smbc_getFunctionClose (backend->smb_context);
976 
977   from_file = smbc_open (backend->smb_context, from_uri,
978 			 O_RDONLY, 0666);
979   if (from_file == NULL || g_vfs_job_is_cancelled (job))
980     goto out;
981 
982   to_file = smbc_open (backend->smb_context, to_uri,
983 		       O_CREAT|O_WRONLY|O_TRUNC, 0666);
984 
985   if (from_file == NULL || g_vfs_job_is_cancelled (job))
986     goto out;
987 
988   while (1)
989     {
990 
991       res = smbc_read (backend->smb_context, from_file,
992 					buffer, sizeof(buffer));
993       if (res < 0 || g_vfs_job_is_cancelled (job))
994 	goto out;
995       if (res == 0)
996 	break; /* Succeeded */
997 
998       buffer_size = res;
999       p = buffer;
1000       while (buffer_size > 0)
1001 	{
1002 	  res = smbc_write (backend->smb_context, to_file,
1003 					     p, buffer_size);
1004 	  if (res < 0 || g_vfs_job_is_cancelled (job))
1005 	    goto out;
1006 	  buffer_size -= res;
1007 	  p += res;
1008 	}
1009     }
1010   succeeded = TRUE;
1011 
1012  out:
1013   if (to_file)
1014 	  smbc_close (backend->smb_context, to_file);
1015   if (from_file)
1016 	  smbc_close (backend->smb_context, from_file);
1017   return succeeded;
1018 }
1019 
1020 static char *
create_etag(struct stat * statbuf)1021 create_etag (struct stat *statbuf)
1022 {
1023   return g_strdup_printf ("%lu", (long unsigned int)statbuf->st_mtime);
1024 }
1025 
1026 static void
do_replace(GVfsBackend * backend,GVfsJobOpenForWrite * job,const char * filename,const char * etag,gboolean make_backup,GFileCreateFlags flags)1027 do_replace (GVfsBackend *backend,
1028 	    GVfsJobOpenForWrite *job,
1029 	    const char *filename,
1030 	    const char *etag,
1031 	    gboolean make_backup,
1032 	    GFileCreateFlags flags)
1033 {
1034   GVfsBackendSmb *op_backend = G_VFS_BACKEND_SMB (backend);
1035   struct stat original_stat;
1036   int res;
1037   char *uri, *tmp_uri, *backup_uri, *current_etag;
1038   SMBCFILE *file;
1039   GError *error = NULL;
1040   SmbWriteHandle *handle;
1041   smbc_open_fn smbc_open;
1042   smbc_stat_fn smbc_stat;
1043 
1044   uri = create_smb_uri (op_backend->server, op_backend->port, op_backend->share, filename);
1045   tmp_uri = NULL;
1046   if (make_backup)
1047     backup_uri = g_strconcat (uri, "~", NULL);
1048   else
1049     backup_uri = NULL;
1050 
1051   smbc_open = smbc_getFunctionOpen (op_backend->smb_context);
1052   smbc_stat = smbc_getFunctionStat (op_backend->smb_context);
1053 
1054   errno = 0;
1055   file = smbc_open (op_backend->smb_context, uri,
1056                     O_CREAT|O_RDWR|O_EXCL, 0);
1057   if (file == NULL && errno != EEXIST)
1058     {
1059       int errsv = fixup_open_errno (errno);
1060 
1061       g_set_error_literal (&error, G_IO_ERROR,
1062 			   g_io_error_from_errno (errsv),
1063 			   g_strerror (errsv));
1064       goto error;
1065     }
1066   else if (file == NULL && errno == EEXIST)
1067     {
1068       if (etag != NULL)
1069 	{
1070 	  res = smbc_stat (op_backend->smb_context, uri, &original_stat);
1071 
1072 	  if (res == 0)
1073 	    {
1074 	      current_etag = create_etag (&original_stat);
1075 	      if (strcmp (etag, current_etag) != 0)
1076 		{
1077 		  g_free (current_etag);
1078 		  g_set_error_literal (&error,
1079 				       G_IO_ERROR,
1080 				       G_IO_ERROR_WRONG_ETAG,
1081                 	               _("The file was externally modified"));
1082 		  goto error;
1083 		}
1084 	      g_free (current_etag);
1085 	    }
1086 	}
1087 
1088       /* Backup strategy:
1089        *
1090        * By default we:
1091        *  1) save to a tmp file (that doesn't exist already)
1092        *  2) rename orig file to backup file
1093        *     (or delete it if no backup)
1094        *  3) rename tmp file to orig file
1095        *
1096        * However, this can fail if we can't write to the directory.
1097        * In that case we just truncate the file, after having
1098        * copied directly to the backup filename.
1099        */
1100 
1101       file = open_tmpfile (op_backend, uri, &tmp_uri);
1102       if (file == NULL)
1103 	{
1104 	  if (make_backup)
1105 	    {
1106 	      if (!copy_file (op_backend, G_VFS_JOB (job), uri, backup_uri))
1107 		{
1108 		  if (g_vfs_job_is_cancelled (G_VFS_JOB (job)))
1109 		    g_set_error_literal (&error,
1110 					 G_IO_ERROR,
1111 					 G_IO_ERROR_CANCELLED,
1112 					 _("Operation was cancelled"));
1113 		  else
1114 		    g_set_error_literal (&error,
1115 					 G_IO_ERROR,
1116 					 G_IO_ERROR_CANT_CREATE_BACKUP,
1117 					 _("Backup file creation failed"));
1118 		  goto error;
1119 		}
1120 	      g_free (backup_uri);
1121 	      backup_uri = NULL;
1122 	    }
1123 
1124 	  errno = 0;
1125 	  file = smbc_open (op_backend->smb_context, uri,
1126                             O_CREAT|O_RDWR|O_TRUNC, 0);
1127 	  if (file == NULL)
1128 	    {
1129               int errsv = fixup_open_errno (errno);
1130 
1131 	      g_set_error_literal (&error, G_IO_ERROR,
1132 				   g_io_error_from_errno (errsv),
1133 				   g_strerror (errsv));
1134 	      goto error;
1135 	    }
1136 	}
1137     }
1138   else
1139     {
1140       /* Doesn't exist. Just write away */
1141       g_free (backup_uri);
1142       backup_uri = NULL;
1143     }
1144 
1145   handle = g_new (SmbWriteHandle, 1);
1146   handle->file = file;
1147   handle->uri = uri;
1148   handle->tmp_uri = tmp_uri;
1149   handle->backup_uri = backup_uri;
1150 
1151   g_vfs_job_open_for_write_set_can_seek (job, TRUE);
1152   g_vfs_job_open_for_write_set_can_truncate (job, TRUE);
1153   g_vfs_job_open_for_write_set_handle (job, handle);
1154   g_vfs_job_succeeded (G_VFS_JOB (job));
1155 
1156   return;
1157 
1158  error:
1159   g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
1160   g_error_free (error);
1161   g_free (backup_uri);
1162   g_free (tmp_uri);
1163   g_free (uri);
1164 }
1165 
1166 
1167 static void
do_write(GVfsBackend * backend,GVfsJobWrite * job,GVfsBackendHandle _handle,char * buffer,gsize buffer_size)1168 do_write (GVfsBackend *backend,
1169 	  GVfsJobWrite *job,
1170 	  GVfsBackendHandle _handle,
1171 	  char *buffer,
1172 	  gsize buffer_size)
1173 {
1174   GVfsBackendSmb *op_backend = G_VFS_BACKEND_SMB (backend);
1175   SmbWriteHandle *handle = _handle;
1176   ssize_t res;
1177   smbc_write_fn smbc_write;
1178 
1179   smbc_write = smbc_getFunctionWrite (op_backend->smb_context);
1180   res = smbc_write (op_backend->smb_context, handle->file,
1181 					buffer, buffer_size);
1182   if (res == -1)
1183     g_vfs_job_failed_from_errno (G_VFS_JOB (job), errno);
1184   else
1185     {
1186       g_vfs_job_write_set_written_size (job, res);
1187       g_vfs_job_succeeded (G_VFS_JOB (job));
1188     }
1189 }
1190 
1191 static void
do_seek_on_write(GVfsBackend * backend,GVfsJobSeekWrite * job,GVfsBackendHandle _handle,goffset offset,GSeekType type)1192 do_seek_on_write (GVfsBackend *backend,
1193 		  GVfsJobSeekWrite *job,
1194 		  GVfsBackendHandle _handle,
1195 		  goffset    offset,
1196 		  GSeekType  type)
1197 {
1198   GVfsBackendSmb *op_backend = G_VFS_BACKEND_SMB (backend);
1199   SmbWriteHandle *handle = _handle;
1200   int whence;
1201   off_t res;
1202   smbc_lseek_fn smbc_lseek;
1203 
1204   if ((whence = gvfs_seek_type_to_lseek (type)) == -1)
1205     {
1206       g_vfs_job_failed (G_VFS_JOB (job),
1207 			G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
1208 			_("Unsupported seek type"));
1209       return;
1210     }
1211 
1212   smbc_lseek = smbc_getFunctionLseek (op_backend->smb_context);
1213   res = smbc_lseek (op_backend->smb_context, handle->file, offset, whence);
1214 
1215   if (res == (off_t)-1)
1216     g_vfs_job_failed_from_errno (G_VFS_JOB (job), errno);
1217   else
1218     {
1219       g_vfs_job_seek_write_set_offset (job, res);
1220       g_vfs_job_succeeded (G_VFS_JOB (job));
1221     }
1222 
1223   return;
1224 }
1225 
1226 static void
do_truncate(GVfsBackend * backend,GVfsJobTruncate * job,GVfsBackendHandle _handle,goffset size)1227 do_truncate (GVfsBackend *backend,
1228              GVfsJobTruncate *job,
1229              GVfsBackendHandle _handle,
1230 	     goffset size)
1231 {
1232   GVfsBackendSmb *op_backend = G_VFS_BACKEND_SMB (backend);
1233   SmbWriteHandle *handle = _handle;
1234   smbc_ftruncate_fn smbc_ftruncate;
1235 
1236   smbc_ftruncate = smbc_getFunctionFtruncate (op_backend->smb_context);
1237   if (smbc_ftruncate (op_backend->smb_context, handle->file, size) == -1)
1238     g_vfs_job_failed_from_errno (G_VFS_JOB (job), errno);
1239   else
1240     g_vfs_job_succeeded (G_VFS_JOB (job));
1241 }
1242 
1243 static void
do_query_info_on_write(GVfsBackend * backend,GVfsJobQueryInfoWrite * job,GVfsBackendHandle _handle,GFileInfo * info,GFileAttributeMatcher * matcher)1244 do_query_info_on_write (GVfsBackend *backend,
1245 			GVfsJobQueryInfoWrite *job,
1246 			GVfsBackendHandle _handle,
1247 			GFileInfo *info,
1248 			GFileAttributeMatcher *matcher)
1249 {
1250   GVfsBackendSmb *op_backend = G_VFS_BACKEND_SMB (backend);
1251   struct stat st = {0};
1252   SmbWriteHandle *handle = _handle;
1253   int res, saved_errno;
1254   smbc_fstat_fn smbc_fstat;
1255 
1256   smbc_fstat = smbc_getFunctionFstat (op_backend->smb_context);
1257   res = smbc_fstat (op_backend->smb_context, handle->file, &st);
1258   saved_errno = errno;
1259 
1260   if (res == 0)
1261     {
1262       set_info_from_stat (op_backend, info, &st, NULL, matcher);
1263 
1264       g_vfs_job_succeeded (G_VFS_JOB (job));
1265     }
1266   else
1267     g_vfs_job_failed_from_errno (G_VFS_JOB (job), saved_errno);
1268 
1269 }
1270 
1271 static void
do_close_write(GVfsBackend * backend,GVfsJobCloseWrite * job,GVfsBackendHandle _handle)1272 do_close_write (GVfsBackend *backend,
1273 		GVfsJobCloseWrite *job,
1274 		GVfsBackendHandle _handle)
1275 {
1276   GVfsBackendSmb *op_backend = G_VFS_BACKEND_SMB (backend);
1277   SmbWriteHandle *handle = _handle;
1278   struct stat stat_at_close;
1279   int stat_res, errsv;
1280   ssize_t res;
1281   smbc_fstat_fn smbc_fstat;
1282   smbc_close_fn smbc_close;
1283   smbc_unlink_fn smbc_unlink;
1284   smbc_rename_fn smbc_rename;
1285 
1286   smbc_fstat = smbc_getFunctionFstat (op_backend->smb_context);
1287   smbc_close = smbc_getFunctionClose (op_backend->smb_context);
1288   smbc_unlink = smbc_getFunctionUnlink (op_backend->smb_context);
1289   smbc_rename = smbc_getFunctionRename (op_backend->smb_context);
1290 
1291   stat_res = smbc_fstat (op_backend->smb_context, handle->file, &stat_at_close);
1292 
1293   res = smbc_close (op_backend->smb_context, handle->file);
1294 
1295   if (res == -1)
1296     {
1297       errsv = errno;
1298       if (handle->tmp_uri)
1299     	  smbc_unlink (op_backend->smb_context, handle->tmp_uri);
1300       g_vfs_job_failed_from_errno (G_VFS_JOB (job), errsv);
1301       goto out;
1302     }
1303 
1304   if (handle->tmp_uri)
1305     {
1306       if (handle->backup_uri)
1307 	{
1308 	  res = smbc_rename (op_backend->smb_context, handle->uri,
1309 						 op_backend->smb_context, handle->backup_uri);
1310 	  if (res ==  -1)
1311 	    {
1312               errsv = errno;
1313               smbc_unlink (op_backend->smb_context, handle->tmp_uri);
1314 	      g_vfs_job_failed (G_VFS_JOB (job),
1315 				G_IO_ERROR, G_IO_ERROR_CANT_CREATE_BACKUP,
1316 				_("Backup file creation failed: %s"), g_strerror (errsv));
1317 	      goto out;
1318 	    }
1319 	}
1320       else
1321         {
1322 	  res = smbc_unlink (op_backend->smb_context, handle->uri);
1323 	  if (res ==  -1)
1324 	    {
1325 	      errsv = errno;
1326 	      smbc_unlink (op_backend->smb_context, handle->tmp_uri);
1327 	      g_vfs_job_failed_from_errno (G_VFS_JOB (job), errsv);
1328 	      goto out;
1329 	    }
1330 	}
1331 
1332       res = smbc_rename (op_backend->smb_context, handle->tmp_uri,
1333 					     op_backend->smb_context, handle->uri);
1334       if (res ==  -1)
1335 	{
1336 	  errsv = errno;
1337 	  smbc_unlink (op_backend->smb_context, handle->tmp_uri);
1338 	  g_vfs_job_failed_from_errno (G_VFS_JOB (job), errsv);
1339 	  goto out;
1340 	}
1341     }
1342 
1343   if (stat_res == 0)
1344     {
1345       char *etag;
1346       etag = create_etag (&stat_at_close);
1347       g_vfs_job_close_write_set_etag (job, etag);
1348       g_free (etag);
1349     }
1350 
1351   g_vfs_job_succeeded (G_VFS_JOB (job));
1352 
1353  out:
1354   smb_write_handle_free (handle);
1355 }
1356 
1357 static void
set_info_from_stat(GVfsBackendSmb * backend,GFileInfo * info,struct stat * statbuf,const char * basename,GFileAttributeMatcher * matcher)1358 set_info_from_stat (GVfsBackendSmb *backend,
1359 		    GFileInfo *info,
1360 		    struct stat *statbuf,
1361 		    const char *basename,
1362 		    GFileAttributeMatcher *matcher)
1363 {
1364   GFileType file_type;
1365   char *display_name;
1366 
1367   if (basename)
1368     {
1369       g_file_info_set_name (info, basename);
1370       if (*basename == '.')
1371 	g_file_info_set_is_hidden (info, TRUE);
1372     }
1373 
1374 
1375   if (basename != NULL &&
1376       g_file_attribute_matcher_matches (matcher,
1377                                         G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME))
1378     {
1379       if (strcmp (basename, "/") == 0)
1380 	display_name = g_strdup_printf (_("%s on %s"), backend->share, backend->server);
1381       else
1382 	display_name = g_filename_display_name (basename);
1383 
1384       if (strstr (display_name, "\357\277\275") != NULL)
1385         {
1386           char *p = display_name;
1387           display_name = g_strconcat (display_name, _(" (invalid encoding)"), NULL);
1388           g_free (p);
1389         }
1390       g_file_info_set_display_name (info, display_name);
1391       g_free (display_name);
1392     }
1393 
1394   if (basename != NULL &&
1395       g_file_attribute_matcher_matches (matcher,
1396                                         G_FILE_ATTRIBUTE_STANDARD_EDIT_NAME))
1397     {
1398       char *edit_name = g_filename_display_name (basename);
1399       g_file_info_set_edit_name (info, edit_name);
1400       g_free (edit_name);
1401     }
1402 
1403 
1404   file_type = G_FILE_TYPE_UNKNOWN;
1405 
1406   if (S_ISREG (statbuf->st_mode))
1407     file_type = G_FILE_TYPE_REGULAR;
1408   else if (S_ISDIR (statbuf->st_mode))
1409     file_type = G_FILE_TYPE_DIRECTORY;
1410   else if (S_ISCHR (statbuf->st_mode) ||
1411 	   S_ISBLK (statbuf->st_mode) ||
1412 	   S_ISFIFO (statbuf->st_mode)
1413 #ifdef S_ISSOCK
1414 	   || S_ISSOCK (statbuf->st_mode)
1415 #endif
1416 	   )
1417     file_type = G_FILE_TYPE_SPECIAL;
1418   else if (S_ISLNK (statbuf->st_mode))
1419     file_type = G_FILE_TYPE_SYMBOLIC_LINK;
1420 
1421   g_file_info_set_file_type (info, file_type);
1422   g_file_info_set_size (info, statbuf->st_size);
1423   g_file_info_set_attribute_uint64 (info,
1424                                     G_FILE_ATTRIBUTE_STANDARD_ALLOCATED_SIZE,
1425                                     statbuf->st_blocks * G_GUINT64_CONSTANT (512));
1426 
1427   g_file_info_set_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_MODIFIED, statbuf->st_mtime);
1428 #if defined (HAVE_STRUCT_STAT_ST_MTIMENSEC)
1429   g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_TIME_MODIFIED_USEC, statbuf->st_mtimensec / 1000);
1430 #elif defined (HAVE_STRUCT_STAT_ST_MTIM_TV_NSEC)
1431   g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_TIME_MODIFIED_USEC, statbuf->st_mtim.tv_nsec / 1000);
1432 #else
1433   g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_TIME_MODIFIED_USEC, 0);
1434 #endif
1435 
1436   if (g_file_attribute_matcher_matches (matcher,
1437 					G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE) ||
1438       g_file_attribute_matcher_matches (matcher,
1439                                         G_FILE_ATTRIBUTE_STANDARD_FAST_CONTENT_TYPE) ||
1440       g_file_attribute_matcher_matches (matcher,
1441 					G_FILE_ATTRIBUTE_STANDARD_ICON) ||
1442       g_file_attribute_matcher_matches (matcher,
1443 					G_FILE_ATTRIBUTE_STANDARD_SYMBOLIC_ICON))
1444     {
1445       GIcon *icon = NULL;
1446       GIcon *symbolic_icon = NULL;
1447       char *content_type = NULL;
1448       gboolean uncertain_content_type = FALSE;
1449 
1450       if (S_ISDIR(statbuf->st_mode))
1451 	{
1452 	  content_type = g_strdup ("inode/directory");
1453 	  if (basename != NULL && strcmp (basename, "/") == 0)
1454             {
1455               icon = g_themed_icon_new ("folder-remote");
1456               symbolic_icon = g_themed_icon_new ("folder-remote-symbolic");
1457             }
1458 	  else
1459             {
1460               icon = g_content_type_get_icon (content_type);
1461               symbolic_icon = g_content_type_get_symbolic_icon (content_type);
1462             }
1463 	}
1464       else if (basename != NULL)
1465 	{
1466 	  content_type = g_content_type_guess (basename, NULL, 0, &uncertain_content_type);
1467 	  if (content_type)
1468             {
1469               icon = g_content_type_get_icon (content_type);
1470               symbolic_icon = g_content_type_get_symbolic_icon (content_type);
1471             }
1472 	}
1473 
1474       if (content_type)
1475 	{
1476 	  if (!uncertain_content_type)
1477 	    g_file_info_set_content_type (info, content_type);
1478 
1479 	  g_file_info_set_attribute_string (info,
1480 					    G_FILE_ATTRIBUTE_STANDARD_FAST_CONTENT_TYPE,
1481 					    content_type);
1482 	  g_free (content_type);
1483 	}
1484 
1485       if (icon == NULL)
1486 	icon = g_themed_icon_new ("text-x-generic");
1487       if (symbolic_icon == NULL)
1488 	symbolic_icon = g_themed_icon_new ("text-x-generic-symbolic");
1489 
1490       g_file_info_set_icon (info, icon);
1491       g_object_unref (icon);
1492       g_file_info_set_symbolic_icon (info, symbolic_icon);
1493       g_object_unref (symbolic_icon);
1494     }
1495 
1496   /* Don't trust n_link, uid, gid, etc returned from libsmb, its just made up.
1497      These are ok though: */
1498 
1499   g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_DEVICE, statbuf->st_dev);
1500   g_file_info_set_attribute_uint64 (info, G_FILE_ATTRIBUTE_UNIX_INODE, statbuf->st_ino);
1501 
1502   /* If file is dos-readonly, libsmbclient doesn't set S_IWUSR, we use this to
1503      trigger ACCESS_WRITE = FALSE. Only set for regular files, see
1504      https://bugzilla.gnome.org/show_bug.cgi?id=598206   */
1505   if (S_ISREG (statbuf->st_mode))
1506     g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE, statbuf->st_mode & S_IWUSR);
1507 
1508   g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_TRASH, FALSE);
1509 
1510   g_file_info_set_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_ACCESS, statbuf->st_atime);
1511 #if defined (HAVE_STRUCT_STAT_ST_ATIMENSEC)
1512   g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_TIME_ACCESS_USEC, statbuf->st_atimensec / 1000);
1513 #elif defined (HAVE_STRUCT_STAT_ST_ATIM_TV_NSEC)
1514   g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_TIME_ACCESS_USEC, statbuf->st_atim.tv_nsec / 1000);
1515 #endif
1516   g_file_info_set_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_CHANGED, statbuf->st_ctime);
1517 #if defined (HAVE_STRUCT_STAT_ST_CTIMENSEC)
1518   g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_TIME_CHANGED_USEC, statbuf->st_ctimensec / 1000);
1519 #elif defined (HAVE_STRUCT_STAT_ST_CTIM_TV_NSEC)
1520   g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_TIME_CHANGED_USEC, statbuf->st_ctim.tv_nsec / 1000);
1521 #endif
1522 
1523   /* Libsmb sets the X bit on files to indicate some special things: */
1524   if ((statbuf->st_mode & S_IFDIR) == 0) {
1525 
1526     if (statbuf->st_mode & S_IXOTH)
1527       g_file_info_set_is_hidden (info, TRUE);
1528 
1529     if (statbuf->st_mode & S_IXUSR)
1530       g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_DOS_IS_ARCHIVE, TRUE);
1531 
1532     if (statbuf->st_mode & S_IXGRP)
1533       g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_DOS_IS_SYSTEM, TRUE);
1534   }
1535 
1536   if (g_file_attribute_matcher_matches (matcher, G_FILE_ATTRIBUTE_ETAG_VALUE))
1537     {
1538       char *etag = create_etag (statbuf);
1539       g_file_info_set_attribute_string (info, G_FILE_ATTRIBUTE_ETAG_VALUE, etag);
1540       g_free (etag);
1541     }
1542 }
1543 
1544 static void
do_query_info(GVfsBackend * backend,GVfsJobQueryInfo * job,const char * filename,GFileQueryInfoFlags flags,GFileInfo * info,GFileAttributeMatcher * matcher)1545 do_query_info (GVfsBackend *backend,
1546 	       GVfsJobQueryInfo *job,
1547 	       const char *filename,
1548 	       GFileQueryInfoFlags flags,
1549 	       GFileInfo *info,
1550 	       GFileAttributeMatcher *matcher)
1551 {
1552   GVfsBackendSmb *op_backend = G_VFS_BACKEND_SMB (backend);
1553   struct stat st = {0};
1554   char *uri;
1555   int res, saved_errno;
1556   char *basename;
1557   smbc_stat_fn smbc_stat;
1558 
1559   uri = create_smb_uri (op_backend->server, op_backend->port, op_backend->share, filename);
1560   smbc_stat = smbc_getFunctionStat (op_backend->smb_context);
1561   res = smbc_stat (op_backend->smb_context, uri, &st);
1562   saved_errno = errno;
1563   g_free (uri);
1564 
1565   if (res == 0)
1566     {
1567       basename = g_path_get_basename (filename);
1568       set_info_from_stat (op_backend, info, &st, basename, matcher);
1569       g_free (basename);
1570 
1571       g_vfs_job_succeeded (G_VFS_JOB (job));
1572     }
1573   else
1574     g_vfs_job_failed_from_errno (G_VFS_JOB (job), saved_errno);
1575 
1576 }
1577 
1578 static void
do_query_fs_info(GVfsBackend * backend,GVfsJobQueryFsInfo * job,const char * filename,GFileInfo * info,GFileAttributeMatcher * attribute_matcher)1579 do_query_fs_info (GVfsBackend *backend,
1580 		  GVfsJobQueryFsInfo *job,
1581 		  const char *filename,
1582 		  GFileInfo *info,
1583 		  GFileAttributeMatcher *attribute_matcher)
1584 {
1585   GVfsBackendSmb *op_backend = G_VFS_BACKEND_SMB (backend);
1586   smbc_statvfs_fn smbc_statvfs;
1587   struct statvfs st = {0};
1588   char *uri;
1589   int res, saved_errno;
1590 
1591   g_file_info_set_attribute_string (info, G_FILE_ATTRIBUTE_FILESYSTEM_TYPE, "cifs");
1592   g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_FILESYSTEM_REMOTE, TRUE);
1593 
1594   if (g_file_attribute_matcher_matches (attribute_matcher,
1595 					G_FILE_ATTRIBUTE_FILESYSTEM_SIZE) ||
1596       g_file_attribute_matcher_matches (attribute_matcher,
1597 					G_FILE_ATTRIBUTE_FILESYSTEM_FREE) ||
1598       g_file_attribute_matcher_matches (attribute_matcher,
1599                                         G_FILE_ATTRIBUTE_FILESYSTEM_USED) ||
1600       g_file_attribute_matcher_matches (attribute_matcher,
1601 					G_FILE_ATTRIBUTE_FILESYSTEM_READONLY))
1602     {
1603       uri = create_smb_uri (op_backend->server, op_backend->port, op_backend->share, filename);
1604       smbc_statvfs = smbc_getFunctionStatVFS (op_backend->smb_context);
1605       res = smbc_statvfs (op_backend->smb_context, uri, &st);
1606       saved_errno = errno;
1607       g_free (uri);
1608 
1609       if (res == 0)
1610         {
1611           /* older samba versions ( < 3.0.28) return zero values in struct statvfs */
1612           if (st.f_blocks > 0)
1613             {
1614               /* FIXME: inconsistent return values (libsmbclient-3.4.2)
1615                *       - for linux samba hosts, f_frsize is zero and f_bsize is a real block size
1616                *       - for some Windows hosts (XP), f_frsize and f_bsize should be multiplied to get real block size
1617                */
1618               guint64 size, free_space;
1619               size = st.f_bsize * st.f_blocks * ((st.f_frsize == 0) ? 1 : st.f_frsize);
1620               free_space = st.f_bsize * st.f_bfree * ((st.f_frsize == 0) ? 1 : st.f_frsize);
1621               g_file_info_set_attribute_uint64 (info, G_FILE_ATTRIBUTE_FILESYSTEM_SIZE, size);
1622               g_file_info_set_attribute_uint64 (info, G_FILE_ATTRIBUTE_FILESYSTEM_FREE, free_space);
1623               g_file_info_set_attribute_uint64 (info, G_FILE_ATTRIBUTE_FILESYSTEM_USED, size - free_space);
1624               g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_FILESYSTEM_READONLY, st.f_flag & SMBC_VFS_FEATURE_RDONLY);
1625             }
1626         }
1627       else
1628         {
1629           g_vfs_job_failed_from_errno (G_VFS_JOB (job), saved_errno);
1630           return;
1631         }
1632     }
1633 
1634   g_vfs_job_succeeded (G_VFS_JOB (job));
1635 }
1636 
1637 
1638 static gboolean
try_query_settable_attributes(GVfsBackend * backend,GVfsJobQueryAttributes * job,const char * filename)1639 try_query_settable_attributes (GVfsBackend *backend,
1640 			       GVfsJobQueryAttributes *job,
1641 			       const char *filename)
1642 {
1643   GFileAttributeInfoList *list;
1644 
1645   list = g_file_attribute_info_list_new ();
1646 
1647   /* TODO: Add all settable attributes here -- bug #559586 */
1648   /* TODO: xattrs support? */
1649 
1650   g_file_attribute_info_list_add (list,
1651                                   G_FILE_ATTRIBUTE_TIME_MODIFIED,
1652                                   G_FILE_ATTRIBUTE_TYPE_UINT64,
1653                                   G_FILE_ATTRIBUTE_INFO_COPY_WITH_FILE |
1654                                   G_FILE_ATTRIBUTE_INFO_COPY_WHEN_MOVED);
1655 
1656 #if 0
1657 /* FIXME: disabled; despite chmod is supported, it makes no sense on samba shares and
1658           libsmbclient lacks proper API to read unix file modes.
1659           The struct stat->st_mode member is used for special Windows attributes. */
1660   g_file_attribute_info_list_add (list,
1661                                   G_FILE_ATTRIBUTE_UNIX_MODE,
1662                                   G_FILE_ATTRIBUTE_TYPE_UINT32,
1663                                   G_FILE_ATTRIBUTE_INFO_COPY_WITH_FILE |
1664                                   G_FILE_ATTRIBUTE_INFO_COPY_WHEN_MOVED);
1665 #endif
1666 
1667   g_vfs_job_query_attributes_set_list (job, list);
1668   g_vfs_job_succeeded (G_VFS_JOB (job));
1669 
1670   g_file_attribute_info_list_unref (list);
1671   return TRUE;
1672 }
1673 
1674 static void
do_set_attribute(GVfsBackend * backend,GVfsJobSetAttribute * job,const char * filename,const char * attribute,GFileAttributeType type,gpointer value_p,GFileQueryInfoFlags flags)1675 do_set_attribute (GVfsBackend *backend,
1676                   GVfsJobSetAttribute *job,
1677                   const char *filename,
1678                   const char *attribute,
1679                   GFileAttributeType type,
1680                   gpointer value_p,
1681                   GFileQueryInfoFlags flags)
1682 {
1683   GVfsBackendSmb *op_backend;
1684   char *uri;
1685   int res, errsv;
1686   struct timeval tbuf[2];
1687   smbc_utimes_fn smbc_utimes;
1688 #if 0
1689   smbc_chmod_fn smbc_chmod;
1690 #endif
1691 
1692 
1693   op_backend = G_VFS_BACKEND_SMB (backend);
1694 
1695   if (strcmp (attribute, G_FILE_ATTRIBUTE_TIME_MODIFIED) != 0
1696 #if 0
1697       && strcmp (attribute, G_FILE_ATTRIBUTE_UNIX_MODE) != 0
1698 #endif
1699       )
1700     {
1701       g_vfs_job_failed (G_VFS_JOB (job),
1702                         G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
1703                         _("Operation not supported"));
1704       return;
1705     }
1706 
1707   uri = create_smb_uri (op_backend->server, op_backend->port, op_backend->share, filename);
1708   res = -1;
1709 
1710   if (strcmp (attribute, G_FILE_ATTRIBUTE_TIME_MODIFIED) == 0)
1711     {
1712       if (type == G_FILE_ATTRIBUTE_TYPE_UINT64)
1713         {
1714 	  smbc_utimes = smbc_getFunctionUtimes (op_backend->smb_context);
1715 	  tbuf[1].tv_sec = (*(guint64 *)value_p);  /* mtime */
1716 	  tbuf[1].tv_usec = 0;
1717 	  /* atime = mtime (atimes are usually disabled on desktop systems) */
1718 	  tbuf[0].tv_sec = tbuf[1].tv_sec;
1719 	  tbuf[0].tv_usec = 0;
1720 	  res = smbc_utimes (op_backend->smb_context, uri, &tbuf[0]);
1721 	}
1722       else
1723         {
1724           g_vfs_job_failed (G_VFS_JOB (job),
1725                             G_IO_ERROR,
1726                             G_IO_ERROR_INVALID_ARGUMENT,
1727                             "%s",
1728                             _("Invalid attribute type (uint64 expected)"));
1729         }
1730     }
1731 #if 0
1732   else
1733   if (strcmp (attribute, G_FILE_ATTRIBUTE_UNIX_MODE) == 0)
1734     {
1735       smbc_chmod = smbc_getFunctionChmod (op_backend->smb_context);
1736       res = smbc_chmod (op_backend->smb_context, uri, (*(guint32 *)value_p) & 0777);
1737     }
1738 #endif
1739 
1740   errsv = errno;
1741   g_free (uri);
1742 
1743   if (res != 0)
1744     g_vfs_job_failed_from_errno (G_VFS_JOB (job), errsv);
1745   else
1746     g_vfs_job_succeeded (G_VFS_JOB (job));
1747 }
1748 
1749 static void
do_enumerate(GVfsBackend * backend,GVfsJobEnumerate * job,const char * filename,GFileAttributeMatcher * matcher,GFileQueryInfoFlags flags)1750 do_enumerate (GVfsBackend *backend,
1751 	      GVfsJobEnumerate *job,
1752 	      const char *filename,
1753 	      GFileAttributeMatcher *matcher,
1754 	      GFileQueryInfoFlags flags)
1755 {
1756   GVfsBackendSmb *op_backend = G_VFS_BACKEND_SMB (backend);
1757   struct stat st = { 0 };
1758   GError *error;
1759   SMBCFILE *dir;
1760   GFileInfo *info;
1761   GString *uri;
1762   smbc_opendir_fn smbc_opendir;
1763   smbc_closedir_fn smbc_closedir;
1764 #ifndef HAVE_SMBC_READDIRPLUS2
1765   int res;
1766   char dirents[1024*4];
1767   struct smbc_dirent *dirp;
1768   int uri_start_len;
1769   smbc_getdents_fn smbc_getdents;
1770   smbc_stat_fn smbc_stat;
1771 #else
1772   smbc_readdirplus2_fn smbc_readdirplus2;
1773   const struct libsmb_file_info *exstat;
1774 #endif
1775 
1776   uri = create_smb_uri_string (op_backend->server, op_backend->port, op_backend->share, filename);
1777 
1778   smbc_opendir = smbc_getFunctionOpendir (op_backend->smb_context);
1779 #ifndef HAVE_SMBC_READDIRPLUS2
1780   smbc_getdents = smbc_getFunctionGetdents (op_backend->smb_context);
1781   smbc_stat = smbc_getFunctionStat (op_backend->smb_context);
1782 #else
1783   smbc_readdirplus2 = smbc_getFunctionReaddirPlus2 (op_backend->smb_context);
1784 #endif
1785   smbc_closedir = smbc_getFunctionClosedir (op_backend->smb_context);
1786 
1787   dir = smbc_opendir (op_backend->smb_context, uri->str);
1788 
1789   if (dir == NULL)
1790     {
1791       int errsv = errno;
1792 
1793       error = NULL;
1794       g_set_error_literal (&error, G_IO_ERROR,
1795 			   g_io_error_from_errno (errsv),
1796 			   g_strerror (errsv));
1797       goto error;
1798     }
1799 
1800   g_vfs_job_succeeded (G_VFS_JOB (job));
1801 
1802   if (uri->str[uri->len - 1] != '/')
1803     g_string_append_c (uri, '/');
1804 
1805 #ifndef HAVE_SMBC_READDIRPLUS2
1806   uri_start_len = uri->len;
1807 
1808   while (TRUE)
1809     {
1810       res = smbc_getdents (op_backend->smb_context, dir, (struct smbc_dirent *)dirents, sizeof (dirents));
1811       if (res <= 0)
1812 	break;
1813 
1814       dirp = (struct smbc_dirent *)dirents;
1815       while (res > 0)
1816 	{
1817 	  unsigned int dirlen;
1818 
1819 	  /* TODO: Only do stat if required for flags */
1820 
1821 	  if ((dirp->smbc_type == SMBC_DIR ||
1822 	       dirp->smbc_type == SMBC_FILE ||
1823 	       dirp->smbc_type == SMBC_LINK) &&
1824 	      strcmp (dirp->name, ".") != 0 &&
1825 	      strcmp (dirp->name, "..") != 0)
1826 	    {
1827 	      int stat_res;
1828 	      g_string_truncate (uri, uri_start_len);
1829               g_string_append_uri_escaped (uri, dirp->name, SUB_DELIM_CHARS ":@/", FALSE);
1830 
1831 	      if (matcher == NULL ||
1832 		  g_file_attribute_matcher_matches_only (matcher, G_FILE_ATTRIBUTE_STANDARD_NAME))
1833 		{
1834 		  info = g_file_info_new ();
1835 		  g_file_info_set_name (info, dirp->name);
1836                   g_vfs_job_enumerate_add_info (job, info);
1837                   g_object_unref (info);
1838 		}
1839 	      else
1840 		{
1841 		  stat_res = smbc_stat (op_backend->smb_context,
1842 							    uri->str, &st);
1843 		  if (stat_res == 0)
1844 		    {
1845 		      info = g_file_info_new ();
1846 		      set_info_from_stat (op_backend, info, &st, dirp->name, matcher);
1847                       g_vfs_job_enumerate_add_info (job, info);
1848                       g_object_unref (info);
1849 		    }
1850 		}
1851 	    }
1852 
1853 	  dirlen = dirp->dirlen;
1854 	  dirp = (struct smbc_dirent *) (((char *)dirp) + dirlen);
1855 	  res -= dirlen;
1856 	}
1857    }
1858 #else
1859   while ((exstat = smbc_readdirplus2 (op_backend->smb_context, dir, &st)) != NULL)
1860     {
1861       if ((S_ISREG (st.st_mode) ||
1862            S_ISDIR (st.st_mode) ||
1863            S_ISLNK (st.st_mode)) &&
1864           g_strcmp0 (exstat->name, ".") != 0 &&
1865           g_strcmp0 (exstat->name, "..") != 0)
1866         {
1867           info = g_file_info_new ();
1868           set_info_from_stat (op_backend, info, &st, exstat->name, matcher);
1869           g_vfs_job_enumerate_add_info (job, info);
1870           g_object_unref (info);
1871         }
1872 
1873       memset (&st, 0, sizeof (struct stat));
1874     }
1875 #endif
1876 
1877   smbc_closedir (op_backend->smb_context, dir);
1878 
1879   g_vfs_job_enumerate_done (job);
1880 
1881   g_string_free (uri, TRUE);
1882   return;
1883 
1884  error:
1885   g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
1886   g_error_free (error);
1887   g_string_free (uri, TRUE);
1888 }
1889 
1890 static void
do_set_display_name(GVfsBackend * backend,GVfsJobSetDisplayName * job,const char * filename,const char * display_name)1891 do_set_display_name (GVfsBackend *backend,
1892 		     GVfsJobSetDisplayName *job,
1893 		     const char *filename,
1894 		     const char *display_name)
1895 {
1896   GVfsBackendSmb *op_backend = G_VFS_BACKEND_SMB (backend);
1897   char *from_uri, *to_uri;
1898   char *dirname, *new_path;
1899   int res, errsv;
1900   struct stat st;
1901   smbc_rename_fn smbc_rename;
1902   smbc_stat_fn smbc_stat;
1903 
1904   dirname = g_path_get_dirname (filename);
1905 
1906   /* TODO: display name is in utf8, atm we assume libsmb uris
1907      are in utf8, but this might not be true if the user changed
1908      the smb.conf file. Can we check this and convert? */
1909 
1910   new_path = g_build_filename (dirname, display_name, NULL);
1911   g_free (dirname);
1912 
1913   from_uri = create_smb_uri (op_backend->server, op_backend->port, op_backend->share, filename);
1914   to_uri = create_smb_uri (op_backend->server, op_backend->port, op_backend->share, new_path);
1915 
1916 
1917   /* We can't rely on libsmbclient reporting EEXIST, let's always stat first.
1918    * https://bugzilla.gnome.org/show_bug.cgi?id=616645
1919    */
1920   smbc_stat = smbc_getFunctionStat (op_backend->smb_context);
1921   res = smbc_stat (op_backend->smb_context, to_uri, &st);
1922   if (res == 0)
1923     {
1924       g_vfs_job_failed (G_VFS_JOB (job),
1925                         G_IO_ERROR, G_IO_ERROR_EXISTS,
1926                         _("Can’t rename file, filename already exists"));
1927       goto out;
1928     }
1929 
1930   smbc_rename = smbc_getFunctionRename (op_backend->smb_context);
1931   res = smbc_rename (op_backend->smb_context, from_uri,
1932                      op_backend->smb_context, to_uri);
1933   errsv = errno;
1934 
1935   if (res != 0)
1936     g_vfs_job_failed_from_errno (G_VFS_JOB (job), errsv);
1937   else
1938     {
1939       g_vfs_job_set_display_name_set_new_path (job, new_path);
1940       g_vfs_job_succeeded (G_VFS_JOB (job));
1941     }
1942 
1943  out:
1944   g_free (from_uri);
1945   g_free (to_uri);
1946   g_free (new_path);
1947 }
1948 
1949 static void
do_delete(GVfsBackend * backend,GVfsJobDelete * job,const char * filename)1950 do_delete (GVfsBackend *backend,
1951 	   GVfsJobDelete *job,
1952 	   const char *filename)
1953 {
1954   GVfsBackendSmb *op_backend = G_VFS_BACKEND_SMB (backend);
1955   struct stat statbuf;
1956   char *uri;
1957   int errsv, res;
1958   smbc_stat_fn smbc_stat;
1959   smbc_rmdir_fn smbc_rmdir;
1960   smbc_unlink_fn smbc_unlink;
1961 
1962 
1963   uri = create_smb_uri (op_backend->server, op_backend->port, op_backend->share, filename);
1964 
1965   smbc_stat = smbc_getFunctionStat (op_backend->smb_context);
1966   smbc_rmdir = smbc_getFunctionRmdir (op_backend->smb_context);
1967   smbc_unlink = smbc_getFunctionUnlink (op_backend->smb_context);
1968 
1969   res = smbc_stat (op_backend->smb_context, uri, &statbuf);
1970   if (res == -1)
1971     {
1972       errsv = errno;
1973 
1974       g_vfs_job_failed (G_VFS_JOB (job),
1975 			G_IO_ERROR,
1976 			g_io_error_from_errno (errsv),
1977 			_("Error deleting file: %s"),
1978 			g_strerror (errsv));
1979       g_free (uri);
1980       return;
1981     }
1982 
1983   if (S_ISDIR (statbuf.st_mode))
1984     {
1985       res = smbc_rmdir (op_backend->smb_context, uri);
1986 
1987       /* We can't rely on libsmbclient reporting ENOTEMPTY, let's verify that
1988        * the dir has been really removed:
1989        * https://bugzilla.samba.org/show_bug.cgi?id=13204
1990        */
1991       if (res == 0 && smbc_stat (op_backend->smb_context, uri, &statbuf) == 0)
1992         {
1993           res = -1;
1994           errno = ENOTEMPTY;
1995         }
1996     }
1997   else
1998     res = smbc_unlink (op_backend->smb_context, uri);
1999   errsv = errno;
2000   g_free (uri);
2001 
2002   if (res != 0)
2003     g_vfs_job_failed_from_errno (G_VFS_JOB (job), errsv);
2004   else
2005     g_vfs_job_succeeded (G_VFS_JOB (job));
2006 }
2007 
2008 static void
do_make_directory(GVfsBackend * backend,GVfsJobMakeDirectory * job,const char * filename)2009 do_make_directory (GVfsBackend *backend,
2010 		   GVfsJobMakeDirectory *job,
2011 		   const char *filename)
2012 {
2013   GVfsBackendSmb *op_backend = G_VFS_BACKEND_SMB (backend);
2014   char *uri;
2015   int errsv, res;
2016   smbc_mkdir_fn smbc_mkdir;
2017 
2018   uri = create_smb_uri (op_backend->server, op_backend->port, op_backend->share, filename);
2019   smbc_mkdir = smbc_getFunctionMkdir (op_backend->smb_context);
2020   res = smbc_mkdir (op_backend->smb_context, uri, 0666);
2021   errsv = errno;
2022   g_free (uri);
2023 
2024   if (res != 0)
2025     g_vfs_job_failed_from_errno (G_VFS_JOB (job), errsv);
2026   else
2027     g_vfs_job_succeeded (G_VFS_JOB (job));
2028 }
2029 
2030 static void
do_move(GVfsBackend * backend,GVfsJobMove * job,const char * source,const char * destination,GFileCopyFlags flags,GFileProgressCallback progress_callback,gpointer progress_callback_data)2031 do_move (GVfsBackend *backend,
2032 	 GVfsJobMove *job,
2033 	 const char *source,
2034 	 const char *destination,
2035 	 GFileCopyFlags flags,
2036 	 GFileProgressCallback progress_callback,
2037 	 gpointer progress_callback_data)
2038 {
2039   GVfsBackendSmb *op_backend = G_VFS_BACKEND_SMB (backend);
2040   char *source_uri, *dest_uri, *backup_uri;
2041   gboolean destination_exist, source_is_dir;
2042   struct stat statbuf;
2043   int res, errsv;
2044   smbc_stat_fn smbc_stat;
2045   smbc_rename_fn smbc_rename;
2046   smbc_unlink_fn smbc_unlink;
2047   goffset size;
2048 
2049 
2050   source_uri = create_smb_uri (op_backend->server, op_backend->port, op_backend->share, source);
2051 
2052   smbc_stat = smbc_getFunctionStat (op_backend->smb_context);
2053   smbc_rename = smbc_getFunctionRename (op_backend->smb_context);
2054   smbc_unlink = smbc_getFunctionUnlink (op_backend->smb_context);
2055 
2056   res = smbc_stat (op_backend->smb_context, source_uri, &statbuf);
2057   if (res == -1)
2058     {
2059       errsv = errno;
2060 
2061       g_vfs_job_failed (G_VFS_JOB (job),
2062 			G_IO_ERROR,
2063 			g_io_error_from_errno (errsv),
2064 			_("Error moving file: %s"),
2065 			g_strerror (errsv));
2066       g_free (source_uri);
2067       return;
2068     }
2069 
2070   source_is_dir = S_ISDIR (statbuf.st_mode);
2071   size = statbuf.st_size;
2072 
2073   dest_uri = create_smb_uri (op_backend->server, op_backend->port, op_backend->share, destination);
2074 
2075   destination_exist = FALSE;
2076   res = smbc_stat (op_backend->smb_context, dest_uri, &statbuf);
2077   if (res == 0)
2078     {
2079       destination_exist = TRUE; /* Target file exists */
2080 
2081       if (flags & G_FILE_COPY_OVERWRITE)
2082 	{
2083 	  /* Always fail on dirs, even with overwrite */
2084 	  if (S_ISDIR (statbuf.st_mode))
2085 	    {
2086 	      g_vfs_job_failed (G_VFS_JOB (job),
2087 				G_IO_ERROR,
2088 				G_IO_ERROR_WOULD_MERGE,
2089 				_("Can’t move directory over directory"));
2090 	      g_free (source_uri);
2091 	      g_free (dest_uri);
2092 	      return;
2093 	    }
2094 	}
2095       else
2096 	{
2097 	  g_vfs_job_failed (G_VFS_JOB (job),
2098 			    G_IO_ERROR,
2099 			    G_IO_ERROR_EXISTS,
2100 			    _("Target file already exists"));
2101 	  g_free (source_uri);
2102 	  g_free (dest_uri);
2103 	  return;
2104 	}
2105     }
2106 
2107   if (flags & G_FILE_COPY_BACKUP && destination_exist)
2108     {
2109       backup_uri = g_strconcat (dest_uri, "~", NULL);
2110       res = smbc_rename (op_backend->smb_context, dest_uri,
2111 					     op_backend->smb_context, backup_uri);
2112       if (res == -1)
2113 	{
2114 	  g_vfs_job_failed (G_VFS_JOB (job),
2115 			    G_IO_ERROR,
2116 			    G_IO_ERROR_CANT_CREATE_BACKUP,
2117 			    _("Backup file creation failed"));
2118 	  g_free (source_uri);
2119 	  g_free (dest_uri);
2120 	  g_free (backup_uri);
2121 	  return;
2122 	}
2123       g_free (backup_uri);
2124       destination_exist = FALSE; /* It did, but no more */
2125     }
2126 
2127   if (source_is_dir && destination_exist && (flags & G_FILE_COPY_OVERWRITE))
2128     {
2129       /* Source is a dir, destination exists (and is not a dir, because that would have failed
2130 	 earlier), and we're overwriting. Manually remove the target so we can do the rename. */
2131       res = smbc_unlink (op_backend->smb_context, dest_uri);
2132       errsv = errno;
2133       if (res == -1)
2134 	{
2135 	  g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR,
2136 			    g_io_error_from_errno (errsv),
2137 			    _("Error removing target file: %s"),
2138 			    g_strerror (errsv));
2139 	  g_free (source_uri);
2140 	  g_free (dest_uri);
2141 	  return;
2142 	}
2143     }
2144 
2145 
2146   res = smbc_rename (op_backend->smb_context, source_uri,
2147 					 op_backend->smb_context, dest_uri);
2148   errsv = errno;
2149   g_free (source_uri);
2150   g_free (dest_uri);
2151 
2152   /* Catch moves across device boundaries */
2153   if (res != 0)
2154     {
2155       if (errsv == EXDEV ||
2156 	  /* Unfortunately libsmbclient doesn't correctly return EXDEV, but falls back
2157 	     to EINVAL, so we try to guess when this happens: */
2158 	  (errsv == EINVAL && source_is_dir))
2159 	g_vfs_job_failed (G_VFS_JOB (job),
2160 			  G_IO_ERROR, G_IO_ERROR_WOULD_RECURSE,
2161 			  _("Can’t recursively move directory"));
2162       else
2163 	g_vfs_job_failed_from_errno (G_VFS_JOB (job), errsv);
2164     }
2165   else
2166     {
2167       g_vfs_job_progress_callback (size, size, job);
2168       g_vfs_job_succeeded (G_VFS_JOB (job));
2169     }
2170 }
2171 
2172 static void
g_vfs_backend_smb_class_init(GVfsBackendSmbClass * klass)2173 g_vfs_backend_smb_class_init (GVfsBackendSmbClass *klass)
2174 {
2175   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
2176   GVfsBackendClass *backend_class = G_VFS_BACKEND_CLASS (klass);
2177 
2178   gobject_class->finalize = g_vfs_backend_smb_finalize;
2179 
2180   backend_class->mount = do_mount;
2181   backend_class->try_mount = try_mount;
2182   backend_class->unmount = do_unmount;
2183   backend_class->open_for_read = do_open_for_read;
2184   backend_class->read = do_read;
2185   backend_class->seek_on_read = do_seek_on_read;
2186   backend_class->query_info_on_read = do_query_info_on_read;
2187   backend_class->close_read = do_close_read;
2188   backend_class->create = do_create;
2189   backend_class->append_to = do_append_to;
2190   backend_class->replace = do_replace;
2191   backend_class->write = do_write;
2192   backend_class->seek_on_write = do_seek_on_write;
2193   backend_class->truncate = do_truncate;
2194   backend_class->query_info_on_write = do_query_info_on_write;
2195   backend_class->close_write = do_close_write;
2196   backend_class->query_info = do_query_info;
2197   backend_class->query_fs_info = do_query_fs_info;
2198   backend_class->enumerate = do_enumerate;
2199   backend_class->set_display_name = do_set_display_name;
2200   backend_class->delete = do_delete;
2201   backend_class->make_directory = do_make_directory;
2202   backend_class->move = do_move;
2203   backend_class->try_query_settable_attributes = try_query_settable_attributes;
2204   backend_class->set_attribute = do_set_attribute;
2205 }
2206 
2207 void
g_vfs_smb_daemon_init(void)2208 g_vfs_smb_daemon_init (void)
2209 {
2210   g_set_application_name (_("Windows Shares File System Service"));
2211 }
2212