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