[PATCH v6] New VFS module vfs_fruit

Jeremy Allison jra at samba.org
Tue Aug 12 16:05:03 MDT 2014


On Wed, Aug 06, 2014 at 03:29:29PM +0200, Ralph Böhme wrote:
> Hi
> 
> here's the latest patchset.
> 
> Review appreciated!

OK, attached is a modified patchset for master, with
my 'Reviewed-by:' attached.

Changes I made from the one you sent.

1). Fixed CREATE VFS API to match the leases
change that went into master.

2). Re-wrote (sorry) much of ad_pack(). You
had the pattern:

	SET_VALUE()
	offset += len;
	if (offset >= bufsize) {
		return false;
	}

That's too late. The correct pattern should be:

	if (offset + len < offset || 		  // CHECK FOR WRAP
			offset + len >= bufsize) {// CHECK FOR BUFFER OVERFLOW
		return false;
	}
	SET_VALUE()
	offset += len;

i.e. you must do the check *FIRST*. Once you've
done the SET_VALUE() it's too late, you've already
overflowed.

3). Fixed any leakage of memory out of the VFS
onto talloc_tos(). It's allowable (I used to do
it all the time :-) but it's bad practice these
days.

4). Ensured that VFS exits where close() is
called save and restore errno before calling
a system call. Otherwise errno can get corrupted.

5). Ensured all stack values are initialized
to "safe" values. I think there were some code
paths where uninitialized variables might
have gotten returned. Certainly something
Coverity might catch.

If you can re-test in your environment and
make sure I haven't broken anything that would
be much appreciated :-).

After that we can get a second Team reviewer
and get the code in for 4.2.0 !

Cheers,

	Jeremy.
-------------- next part --------------
From ca97a912b4847af317b929fee86eacd36e3af4a8 Mon Sep 17 00:00:00 2001
From: Ralph Boehme <rb at sernet.de>
Date: Thu, 10 Jul 2014 16:32:15 +0200
Subject: [PATCH 01/10] Fix AFP_BackupTime byte order and use ISO C99 integer
 types

AFP_BackupTime value must be 0x80000000 and all existing defines use
native byte order, not byte swapped.

Signed-off-by: Ralph Boehme <rb at sernet.de>
Reviewed-by: Jeremy Allison <jra at samba.org>
---
 source3/include/MacExtensions.h | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/source3/include/MacExtensions.h b/source3/include/MacExtensions.h
index a60f894..5b00998 100644
--- a/source3/include/MacExtensions.h
+++ b/source3/include/MacExtensions.h
@@ -49,7 +49,7 @@
 #define AFP_INFO_SIZE		0x3c
 #define AFP_Signature		0x41465000 
 #define AFP_Version			0x00000100
-#define AFP_BackupTime		0x00000080
+#define AFP_BackupTime		0x80000000
 #define AFP_FinderSize		32
 /*
 ** Orginal AFP_AfpInfo stream used by NT 
@@ -59,10 +59,10 @@
 */
 typedef struct _AfpInfo
 {
-	 uint32       	afpi_Signature;   		/* Must be *(PDWORD)"AFP" */
-	 uint32       	afpi_Version;     		/* Must be 0x00010000 */
-	 uint32       	afpi_Reserved1;
-	 uint32       	afpi_BackupTime;  		/* Backup time for the file/dir */
+	 uint32_t      	afpi_Signature;   		/* Must be *(PDWORD)"AFP" */
+	 uint32_t      	afpi_Version;     		/* Must be 0x00010000 */
+	 uint32_t      	afpi_Reserved1;
+	 uint32_t      	afpi_BackupTime;  		/* Backup time for the file/dir */
 	 unsigned char 	afpi_FinderInfo[AFP_FinderSize];  	/* Finder Info (32 bytes) */
 	 unsigned char 	afpi_ProDosInfo[6];  	/* ProDos Info (6 bytes) # */
 	 unsigned char 	afpi_Reserved2[6];
-- 
2.0.0.526.g5318336


From ae17f8917f36737554a213f312095c107e9ae344 Mon Sep 17 00:00:00 2001
From: Ralph Boehme <rb at sernet.de>
Date: Mon, 23 Jun 2014 16:59:45 +0200
Subject: [PATCH 02/10] New VFS module vfs_fruit

This module provides enhanced compatibility with Apple SMB clients and
interoperability with a Netatalk 3 AFP fileserver.

The module intercepts the OS X special streams "AFP_AfpInfo" and
"AFP_Resource" and handles them in a special way. All other named
streams are deferred to vfs_streams_xattr.

The OS X client maps all NTFS illegal characters to the Unicode
private range. This module optionally stores the charcters using their
native ASCII encoding.

Open modes are optionally checked against Netatalk AFP share modes.

The "AFP_AfpInfo" named stream is a binary blob containing OS X
extended metadata for files and directories. This module optionally
reads and stores this metadata in a way compatible with Netatalk 3
which stores the metadata in an EA "org.netatalk.metadata". Cf
source3/include/MacExtensions.h for a description of the binary blobs
content.

The "AFP_Resource" named stream may be arbitrarily large, thus it
can't be stored in an EA on most filesystem. ZFS on Solaris is an
exception to the rule, because it there EAs can be of any size and EAs
are first-class filesystem objects that can be used with normal file
syscalls like open(), read(), write(), fcntl() asf. This module stores
the AFP_Resource stream in an AppleDouble file, prepending "._" to the
filename. On Solaris and ZFS the stream is optionally stored in an EA
"org.netatalk.ResourceFork".

Signed-off-by: Ralph Boehme <rb at sernet.de>
Reviewed-by: Jeremy Allison <jra at samba.org>
---
 source3/modules/vfs_fruit.c   | 2946 +++++++++++++++++++++++++++++++++++++++++
 source3/modules/wscript_build |    8 +
 source3/wscript               |    2 +-
 3 files changed, 2955 insertions(+), 1 deletion(-)
 create mode 100644 source3/modules/vfs_fruit.c

diff --git a/source3/modules/vfs_fruit.c b/source3/modules/vfs_fruit.c
new file mode 100644
index 0000000..3783758
--- /dev/null
+++ b/source3/modules/vfs_fruit.c
@@ -0,0 +1,2946 @@
+/*
+ * OS X and Netatalk interoperability VFS module for Samba-3.x
+ *
+ * Copyright (C) Ralph Boehme, 2013, 2014
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "MacExtensions.h"
+#include "smbd/smbd.h"
+#include "system/filesys.h"
+#include "lib/util/time.h"
+#include "../lib/crypto/md5.h"
+#include "system/shmem.h"
+#include "locking/proto.h"
+#include "smbd/globals.h"
+#include "messages.h"
+#include "libcli/security/security.h"
+
+/*
+ * Enhanced OS X and Netatalk compatibility
+ * ========================================
+ *
+ * This modules takes advantage of vfs_streams_xattr and
+ * vfs_catia. VFS modules vfs_fruit and vfs_streams_xattr must be
+ * loaded in the correct order:
+ *
+ *   vfs modules = catia fruit streams_xattr
+ *
+ * The module intercepts the OS X special streams "AFP_AfpInfo" and
+ * "AFP_Resource" and handles them in a special way. All other named
+ * streams are deferred to vfs_streams_xattr.
+ *
+ * The OS X client maps all NTFS illegal characters to the Unicode
+ * private range. This module optionally stores the charcters using
+ * their native ASCII encoding using vfs_catia. If you're not enabling
+ * this feature, you can skip catia from vfs modules.
+ *
+ * Finally, open modes are optionally checked against Netatalk AFP
+ * share modes.
+ *
+ * The "AFP_AfpInfo" named stream is a binary blob containing OS X
+ * extended metadata for files and directories. This module optionally
+ * reads and stores this metadata in a way compatible with Netatalk 3
+ * which stores the metadata in an EA "org.netatalk.metadata". Cf
+ * source3/include/MacExtensions.h for a description of the binary
+ * blobs content.
+ *
+ * The "AFP_Resource" named stream may be arbitrarily large, thus it
+ * can't be stored in an xattr on most filesystem. ZFS on Solaris is
+ * the only available filesystem where xattrs can be of any size and
+ * the OS supports using the file APIs for xattrs.
+ *
+ * The AFP_Resource stream is stored in an AppleDouble file prepending
+ * "._" to the filename. On Solaris with ZFS the stream is optionally
+ * stored in an EA "org.netatalk.ressource".
+ *
+ *
+ * Extended Attributes
+ * ===================
+ *
+ * The OS X SMB client sends xattrs as ADS too. For xattr interop with
+ * other protocols you may want to adjust the xattr names the VFS
+ * module vfs_streams_xattr uses for storing ADS's. This defaults to
+ * user.DosStream.ADS_NAME:$DATA and can be changed by specifying
+ * these module parameters:
+ *
+ *   streams_xattr:prefix = user.
+ *   streams_xattr:store_stream_type = false
+ *
+ *
+ * TODO
+ * ====
+ *
+ * - log diagnostic if any needed VFS module is not loaded
+ *   (eg with lp_vfs_objects())
+ * - add tests
+ */
+
+static int vfs_fruit_debug_level = DBGC_VFS;
+
+#undef DBGC_CLASS
+#define DBGC_CLASS vfs_fruit_debug_level
+
+#define FRUIT_PARAM_TYPE_NAME "fruit"
+#define ADOUBLE_NAME_PREFIX "._"
+
+/*
+ * REVIEW:
+ * This is hokey, but what else can we do?
+ */
+#if defined(HAVE_ATTROPEN) || defined(FREEBSD)
+#define AFPINFO_EA_NETATALK "org.netatalk.Metadata"
+#define AFPRESOURCE_EA_NETATALK "org.netatalk.ResourceFork"
+#else
+#define AFPINFO_EA_NETATALK "user.org.netatalk.Metadata"
+#define AFPRESOURCE_EA_NETATALK "user.org.netatalk.ResourceFork"
+#endif
+
+enum apple_fork {APPLE_FORK_DATA, APPLE_FORK_RSRC};
+
+enum fruit_rsrc {FRUIT_RSRC_STREAM, FRUIT_RSRC_ADFILE, FRUIT_RSRC_XATTR};
+enum fruit_meta {FRUIT_META_STREAM, FRUIT_META_NETATALK};
+enum fruit_locking {FRUIT_LOCKING_NETATALK, FRUIT_LOCKING_NONE};
+enum fruit_encoding {FRUIT_ENC_NATIVE, FRUIT_ENC_PRIVATE};
+
+struct fruit_config_data {
+	enum fruit_rsrc rsrc;
+	enum fruit_meta meta;
+	enum fruit_locking locking;
+	enum fruit_encoding encoding;
+};
+
+static const struct enum_list fruit_rsrc[] = {
+	{FRUIT_RSRC_STREAM, "stream"}, /* pass on to vfs_streams_xattr */
+	{FRUIT_RSRC_ADFILE, "file"}, /* ._ AppleDouble file */
+	{FRUIT_RSRC_XATTR, "xattr"}, /* Netatalk compatible xattr (ZFS only) */
+	{ -1, NULL}
+};
+
+static const struct enum_list fruit_meta[] = {
+	{FRUIT_META_STREAM, "stream"}, /* pass on to vfs_streams_xattr */
+	{FRUIT_META_NETATALK, "netatalk"}, /* Netatalk compatible xattr */
+	{ -1, NULL}
+};
+
+static const struct enum_list fruit_locking[] = {
+	{FRUIT_LOCKING_NETATALK, "netatalk"}, /* synchronize locks with Netatalk */
+	{FRUIT_LOCKING_NONE, "none"},
+	{ -1, NULL}
+};
+
+static const struct enum_list fruit_encoding[] = {
+	{FRUIT_ENC_NATIVE, "native"}, /* map unicode private chars to ASCII */
+	{FRUIT_ENC_PRIVATE, "private"}, /* keep unicode private chars */
+	{ -1, NULL}
+};
+
+/*****************************************************************************
+ * Defines, functions and data structures that deal with AppleDouble
+ *****************************************************************************/
+
+/*
+ * There are two AppleDouble blobs we deal with:
+ *
+ * - ADOUBLE_META - AppleDouble blob used by Netatalk for storing
+ *   metadata in an xattr
+ *
+ * - ADOUBLE_RSRC - AppleDouble blob used by OS X and Netatalk in
+ *   ._ files
+ */
+typedef enum {ADOUBLE_META, ADOUBLE_RSRC} adouble_type_t;
+
+/* Version info */
+#define AD_VERSION2     0x00020000
+#define AD_VERSION      AD_VERSION2
+
+/*
+ * AppleDouble entry IDs.
+ */
+#define ADEID_DFORK         1
+#define ADEID_RFORK         2
+#define ADEID_NAME          3
+#define ADEID_COMMENT       4
+#define ADEID_ICONBW        5
+#define ADEID_ICONCOL       6
+#define ADEID_FILEI         7
+#define ADEID_FILEDATESI    8
+#define ADEID_FINDERI       9
+#define ADEID_MACFILEI      10
+#define ADEID_PRODOSFILEI   11
+#define ADEID_MSDOSFILEI    12
+#define ADEID_SHORTNAME     13
+#define ADEID_AFPFILEI      14
+#define ADEID_DID           15
+
+/* Private Netatalk entries */
+#define ADEID_PRIVDEV       16
+#define ADEID_PRIVINO       17
+#define ADEID_PRIVSYN       18
+#define ADEID_PRIVID        19
+#define ADEID_MAX           (ADEID_PRIVID + 1)
+
+/*
+ * These are the real ids for the private entries,
+ * as stored in the adouble file
+ */
+#define AD_DEV              0x80444556
+#define AD_INO              0x80494E4F
+#define AD_SYN              0x8053594E
+#define AD_ID               0x8053567E
+
+/* Number of actually used entries */
+#define ADEID_NUM_XATTR      8
+#define ADEID_NUM_DOT_UND    2
+#define ADEID_NUM_RSRC_XATTR 1
+
+/* AppleDouble magic */
+#define AD_APPLESINGLE_MAGIC 0x00051600
+#define AD_APPLEDOUBLE_MAGIC 0x00051607
+#define AD_MAGIC             AD_APPLEDOUBLE_MAGIC
+
+/* Sizes of relevant entry bits */
+#define ADEDLEN_MAGIC       4
+#define ADEDLEN_VERSION     4
+#define ADEDLEN_FILLER      16
+#define AD_FILLER_TAG       "Netatalk        " /* should be 16 bytes */
+#define ADEDLEN_NENTRIES    2
+#define AD_HEADER_LEN       (ADEDLEN_MAGIC + ADEDLEN_VERSION + \
+			     ADEDLEN_FILLER + ADEDLEN_NENTRIES) /* 26 */
+#define AD_ENTRY_LEN_EID    4
+#define AD_ENTRY_LEN_OFF    4
+#define AD_ENTRY_LEN_LEN    4
+#define AD_ENTRY_LEN (AD_ENTRY_LEN_EID + AD_ENTRY_LEN_OFF + AD_ENTRY_LEN_LEN)
+
+/* Field widths */
+#define ADEDLEN_NAME            255
+#define ADEDLEN_COMMENT         200
+#define ADEDLEN_FILEI           16
+#define ADEDLEN_FINDERI         32
+#define ADEDLEN_FILEDATESI      16
+#define ADEDLEN_SHORTNAME       12 /* length up to 8.3 */
+#define ADEDLEN_AFPFILEI        4
+#define ADEDLEN_MACFILEI        4
+#define ADEDLEN_PRODOSFILEI     8
+#define ADEDLEN_MSDOSFILEI      2
+#define ADEDLEN_DID             4
+#define ADEDLEN_PRIVDEV         8
+#define ADEDLEN_PRIVINO         8
+#define ADEDLEN_PRIVSYN         8
+#define ADEDLEN_PRIVID          4
+
+/* Offsets */
+#define ADEDOFF_MAGIC         0
+#define ADEDOFF_VERSION       (ADEDOFF_MAGIC + ADEDLEN_MAGIC)
+#define ADEDOFF_FILLER        (ADEDOFF_VERSION + ADEDLEN_VERSION)
+#define ADEDOFF_NENTRIES      (ADEDOFF_FILLER + ADEDLEN_FILLER)
+
+#define ADEDOFF_FINDERI_XATTR    (AD_HEADER_LEN + \
+				  (ADEID_NUM_XATTR * AD_ENTRY_LEN))
+#define ADEDOFF_COMMENT_XATTR    (ADEDOFF_FINDERI_XATTR    + ADEDLEN_FINDERI)
+#define ADEDOFF_FILEDATESI_XATTR (ADEDOFF_COMMENT_XATTR    + ADEDLEN_COMMENT)
+#define ADEDOFF_AFPFILEI_XATTR   (ADEDOFF_FILEDATESI_XATTR + \
+				  ADEDLEN_FILEDATESI)
+#define ADEDOFF_PRIVDEV_XATTR    (ADEDOFF_AFPFILEI_XATTR   + ADEDLEN_AFPFILEI)
+#define ADEDOFF_PRIVINO_XATTR    (ADEDOFF_PRIVDEV_XATTR    + ADEDLEN_PRIVDEV)
+#define ADEDOFF_PRIVSYN_XATTR    (ADEDOFF_PRIVINO_XATTR    + ADEDLEN_PRIVINO)
+#define ADEDOFF_PRIVID_XATTR     (ADEDOFF_PRIVSYN_XATTR    + ADEDLEN_PRIVSYN)
+
+#define ADEDOFF_FINDERI_DOT_UND  (AD_HEADER_LEN + \
+				  (ADEID_NUM_DOT_UND * AD_ENTRY_LEN))
+#define ADEDOFF_RFORK_DOT_UND    (ADEDOFF_FINDERI_DOT_UND + ADEDLEN_FINDERI)
+
+#define AD_DATASZ_XATTR (AD_HEADER_LEN + \
+			 (ADEID_NUM_XATTR * AD_ENTRY_LEN) + \
+			 ADEDLEN_FINDERI + ADEDLEN_COMMENT + \
+			 ADEDLEN_FILEDATESI + ADEDLEN_AFPFILEI + \
+			 ADEDLEN_PRIVDEV + ADEDLEN_PRIVINO + \
+			 ADEDLEN_PRIVSYN + ADEDLEN_PRIVID)
+
+#if AD_DATASZ_XATTR != 402
+#error bad size for AD_DATASZ_XATTR
+#endif
+
+#define AD_DATASZ_DOT_UND (AD_HEADER_LEN + \
+			   (ADEID_NUM_DOT_UND * AD_ENTRY_LEN) + \
+			   ADEDLEN_FINDERI)
+#if AD_DATASZ_DOT_UND != 82
+#error bad size for AD_DATASZ_DOT_UND
+#endif
+
+/*
+ * Sharemode locks fcntl() offsets
+ */
+#if _FILE_OFFSET_BITS == 64 || defined(HAVE_LARGEFILE)
+#define AD_FILELOCK_BASE (UINT64_C(0x7FFFFFFFFFFFFFFF) - 9)
+#else
+#define AD_FILELOCK_BASE (UINT32_C(0x7FFFFFFF) - 9)
+#endif
+#define BYTELOCK_MAX (AD_FILELOCK_BASE - 1)
+
+#define AD_FILELOCK_OPEN_WR        (AD_FILELOCK_BASE + 0)
+#define AD_FILELOCK_OPEN_RD        (AD_FILELOCK_BASE + 1)
+#define AD_FILELOCK_RSRC_OPEN_WR   (AD_FILELOCK_BASE + 2)
+#define AD_FILELOCK_RSRC_OPEN_RD   (AD_FILELOCK_BASE + 3)
+#define AD_FILELOCK_DENY_WR        (AD_FILELOCK_BASE + 4)
+#define AD_FILELOCK_DENY_RD        (AD_FILELOCK_BASE + 5)
+#define AD_FILELOCK_RSRC_DENY_WR   (AD_FILELOCK_BASE + 6)
+#define AD_FILELOCK_RSRC_DENY_RD   (AD_FILELOCK_BASE + 7)
+#define AD_FILELOCK_OPEN_NONE      (AD_FILELOCK_BASE + 8)
+#define AD_FILELOCK_RSRC_OPEN_NONE (AD_FILELOCK_BASE + 9)
+
+/* Time stuff we overload the bits a little */
+#define AD_DATE_CREATE         0
+#define AD_DATE_MODIFY         4
+#define AD_DATE_BACKUP         8
+#define AD_DATE_ACCESS        12
+#define AD_DATE_MASK          (AD_DATE_CREATE | AD_DATE_MODIFY | \
+                               AD_DATE_BACKUP | AD_DATE_ACCESS)
+#define AD_DATE_UNIX          (1 << 10)
+#define AD_DATE_START         0x80000000
+#define AD_DATE_DELTA         946684800
+#define AD_DATE_FROM_UNIX(x)  (htonl((x) - AD_DATE_DELTA))
+#define AD_DATE_TO_UNIX(x)    (ntohl(x) + AD_DATE_DELTA)
+
+/* Accessor macros */
+#define ad_getentrylen(ad,eid)     ((ad)->ad_eid[(eid)].ade_len)
+#define ad_getentryoff(ad,eid)     ((ad)->ad_eid[(eid)].ade_off)
+#define ad_setentrylen(ad,eid,len) ((ad)->ad_eid[(eid)].ade_len = (len))
+#define ad_setentryoff(ad,eid,off) ((ad)->ad_eid[(eid)].ade_off = (off))
+#define ad_entry(ad,eid)           ((ad)->ad_data + ad_getentryoff((ad),(eid)))
+
+struct ad_entry {
+	size_t ade_off;
+	size_t ade_len;
+};
+
+struct adouble {
+	vfs_handle_struct        *ad_handle;
+	files_struct             *ad_fsp;
+	adouble_type_t            ad_type;
+	uint32_t                  ad_magic;
+	uint32_t                  ad_version;
+	struct ad_entry           ad_eid[ADEID_MAX];
+	char                     *ad_data;
+};
+
+struct ad_entry_order {
+	uint32_t id, offset, len;
+};
+
+/* Netatalk AppleDouble metadata xattr */
+static const
+struct ad_entry_order entry_order_meta_xattr[ADEID_NUM_XATTR + 1] = {
+	{ADEID_FINDERI,    ADEDOFF_FINDERI_XATTR,    ADEDLEN_FINDERI},
+	{ADEID_COMMENT,    ADEDOFF_COMMENT_XATTR,    0},
+	{ADEID_FILEDATESI, ADEDOFF_FILEDATESI_XATTR, ADEDLEN_FILEDATESI},
+	{ADEID_AFPFILEI,   ADEDOFF_AFPFILEI_XATTR,   ADEDLEN_AFPFILEI},
+	{ADEID_PRIVDEV,    ADEDOFF_PRIVDEV_XATTR,    0},
+	{ADEID_PRIVINO,    ADEDOFF_PRIVINO_XATTR,    0},
+	{ADEID_PRIVSYN,    ADEDOFF_PRIVSYN_XATTR,    0},
+	{ADEID_PRIVID,     ADEDOFF_PRIVID_XATTR,     0},
+	{0, 0, 0}
+};
+
+/* AppleDouble ressource fork file (the ones prefixed by "._") */
+static const
+struct ad_entry_order entry_order_dot_und[ADEID_NUM_DOT_UND + 1] = {
+	{ADEID_FINDERI,    ADEDOFF_FINDERI_DOT_UND,  ADEDLEN_FINDERI},
+	{ADEID_RFORK,      ADEDOFF_RFORK_DOT_UND,    0},
+	{0, 0, 0}
+};
+
+/*
+ * Fake AppleDouble entry oder for ressource fork xattr.  The xattr
+ * isn't an AppleDouble file, it simply contains the ressource data,
+ * but in order to be able to use some API calls like ad_getentryoff()
+ * we build a fake/helper struct adouble with this entry order struct.
+ */
+static const
+struct ad_entry_order entry_order_rsrc_xattr[ADEID_NUM_RSRC_XATTR + 1] = {
+	{ADEID_RFORK, 0, 0},
+	{0, 0, 0}
+};
+
+/* Conversion from enumerated id to on-disk AppleDouble id */
+#define AD_EID_DISK(a) (set_eid[a])
+static const uint32_t set_eid[] = {
+	0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
+	AD_DEV, AD_INO, AD_SYN, AD_ID
+};
+
+/*
+ * Forward declarations
+ */
+static struct adouble *ad_init(TALLOC_CTX *ctx, vfs_handle_struct *handle,
+			       adouble_type_t type, files_struct *fsp);
+static int ad_write(struct adouble *ad, const char *path);
+static int adouble_path(TALLOC_CTX *ctx, const char *path_in, char **path_out);
+
+/**
+ * Get a date
+ **/
+static int ad_getdate(const struct adouble *ad,
+		      unsigned int dateoff,
+		      uint32_t *date)
+{
+	bool xlate = (dateoff & AD_DATE_UNIX);
+
+	dateoff &= AD_DATE_MASK;
+	if (!ad_getentryoff(ad, ADEID_FILEDATESI)) {
+		return -1;
+	}
+
+	if (dateoff > AD_DATE_ACCESS) {
+	    return -1;
+	}
+	memcpy(date,
+	       ad_entry(ad, ADEID_FILEDATESI) + dateoff,
+	       sizeof(uint32_t));
+
+	if (xlate) {
+		*date = AD_DATE_TO_UNIX(*date);
+	}
+	return 0;
+}
+
+/**
+ * Set a date
+ **/
+static int ad_setdate(struct adouble *ad, unsigned int dateoff, uint32_t date)
+{
+	bool xlate = (dateoff & AD_DATE_UNIX);
+
+	if (!ad_getentryoff(ad, ADEID_FILEDATESI)) {
+		return 0;
+	}
+
+	dateoff &= AD_DATE_MASK;
+	if (xlate) {
+		date = AD_DATE_FROM_UNIX(date);
+	}
+
+	if (dateoff > AD_DATE_ACCESS) {
+		return -1;
+	}
+
+	memcpy(ad_entry(ad, ADEID_FILEDATESI) + dateoff, &date, sizeof(date));
+
+	return 0;
+}
+
+
+/**
+ * Map on-disk AppleDouble id to enumerated id
+ **/
+static uint32_t get_eid(uint32_t eid)
+{
+	if (eid <= 15) {
+		return eid;
+	}
+
+	switch (eid) {
+	case AD_DEV:
+		return ADEID_PRIVDEV;
+	case AD_INO:
+		return ADEID_PRIVINO;
+	case AD_SYN:
+		return ADEID_PRIVSYN;
+	case AD_ID:
+		return ADEID_PRIVID;
+	default:
+		break;
+	}
+
+	return 0;
+}
+
+/**
+ * Pack AppleDouble structure into data buffer
+ **/
+static bool ad_pack(struct adouble *ad)
+{
+	uint32_t       eid;
+	uint16_t       nent;
+	uint32_t       bufsize;
+	uint32_t       offset = 0;
+
+	bufsize = talloc_get_size(ad->ad_data);
+
+	if (offset + ADEDLEN_MAGIC < offset ||
+			offset + ADEDLEN_MAGIC >= bufsize) {
+		return false;
+	}
+	RSIVAL(ad->ad_data, offset, ad->ad_magic);
+	offset += ADEDLEN_MAGIC;
+
+	if (offset + ADEDLEN_VERSION < offset ||
+			offset + ADEDLEN_VERSION >= bufsize) {
+		return false;
+	}
+	RSIVAL(ad->ad_data, offset, ad->ad_version);
+	offset += ADEDLEN_VERSION;
+
+	if (offset + ADEDLEN_FILLER < offset ||
+			offset + ADEDLEN_FILLER >= bufsize) {
+		return false;
+	}
+	if (ad->ad_type == ADOUBLE_RSRC) {
+		memcpy(ad->ad_data + offset, AD_FILLER_TAG, ADEDLEN_FILLER);
+	}
+	offset += ADEDLEN_FILLER;
+
+	if (offset + ADEDLEN_NENTRIES < offset ||
+			offset + ADEDLEN_NENTRIES >= bufsize) {
+		return false;
+	}
+	offset += ADEDLEN_NENTRIES;
+
+	for (eid = 0, nent = 0; eid < ADEID_MAX; eid++) {
+		if ((ad->ad_eid[eid].ade_off == 0)) {
+			/*
+			 * ade_off is also used as indicator whether a
+			 * specific entry is used or not
+			 */
+			continue;
+		}
+
+		if (offset + AD_ENTRY_LEN_EID < offset ||
+				offset + AD_ENTRY_LEN_EID >= bufsize) {
+			return false;
+		}
+		RSIVAL(ad->ad_data, offset, AD_EID_DISK(eid));
+		offset += AD_ENTRY_LEN_EID;
+
+		if (offset + AD_ENTRY_LEN_OFF < offset ||
+				offset + AD_ENTRY_LEN_OFF >= bufsize) {
+			return false;
+		}
+		RSIVAL(ad->ad_data, offset, ad->ad_eid[eid].ade_off);
+		offset += AD_ENTRY_LEN_OFF;
+
+		if (offset + AD_ENTRY_LEN_LEN < offset ||
+				offset + AD_ENTRY_LEN_LEN >= bufsize) {
+			return false;
+		}
+		RSIVAL(ad->ad_data, offset, ad->ad_eid[eid].ade_len);
+		offset += AD_ENTRY_LEN_LEN;
+
+		nent++;
+	}
+
+	if (ADEDOFF_NENTRIES + 2 >= bufsize) {
+		return false;
+	}
+	RSSVAL(ad->ad_data, ADEDOFF_NENTRIES, nent);
+
+	return 0;
+}
+
+/**
+ * Unpack an AppleDouble blob into a struct adoble
+ **/
+static bool ad_unpack(struct adouble *ad, const int nentries)
+{
+	size_t bufsize = talloc_get_size(ad->ad_data);
+	int adentries, i;
+	uint32_t eid, len, off;
+
+	/*
+	 * The size of the buffer ad->ad_data is checked when read, so
+	 * we wouldn't have to check our own offsets, a few extra
+	 * checks won't hurt though. We have to check the offsets we
+	 * read from the buffer anyway.
+	 */
+
+	if (bufsize < (AD_HEADER_LEN + (AD_ENTRY_LEN * nentries))) {
+		DEBUG(1, ("bad size\n"));
+		return false;
+	}
+
+	ad->ad_magic = RIVAL(ad->ad_data, 0);
+	ad->ad_version = RIVAL(ad->ad_data, ADEDOFF_VERSION);
+	if ((ad->ad_magic != AD_MAGIC) || (ad->ad_version != AD_VERSION)) {
+		DEBUG(1, ("wrong magic or version\n"));
+		return false;
+	}
+
+	adentries = RSVAL(ad->ad_data, ADEDOFF_NENTRIES);
+	if (adentries != nentries) {
+		DEBUG(1, ("invalid number of entries: %d\n", adentries));
+		return false;
+	}
+
+	/* now, read in the entry bits */
+	for (i = 0; i < adentries; i++) {
+		eid = RIVAL(ad->ad_data, AD_HEADER_LEN + (i * AD_ENTRY_LEN));
+		eid = get_eid(eid);
+		off = RIVAL(ad->ad_data, AD_HEADER_LEN + (i * AD_ENTRY_LEN) + 4);
+		len = RIVAL(ad->ad_data, AD_HEADER_LEN + (i * AD_ENTRY_LEN) + 8);
+
+		if (!eid || eid > ADEID_MAX) {
+			DEBUG(1, ("bogus eid %d\n", eid));
+			return false;
+		}
+
+		if (off > bufsize) {
+			DEBUG(1, ("bogus eid %d: off: %" PRIu32 ", len: %" PRIu32 "\n",
+				  eid, off, len));
+			return false;
+		}
+		if ((eid != ADEID_RFORK) && ((off + len) > bufsize)) {
+			DEBUG(1, ("bogus eid %d: off: %" PRIu32 ", len: %" PRIu32 "\n",
+				  eid, off, len));
+			return false;
+		}
+
+		ad->ad_eid[eid].ade_off = off;
+		ad->ad_eid[eid].ade_len = len;
+	}
+
+	return true;
+}
+
+/**
+ * Convert from Apple's ._ file to Netatalk
+ *
+ * Apple's AppleDouble may contain a FinderInfo entry longer then 32
+ * bytes containing packed xattrs. Netatalk can't deal with that, so
+ * we simply discard the packed xattrs.
+ *
+ * @return -1 in case an error occured, 0 if no conversion was done, 1
+ * otherwise
+ **/
+static int ad_convert(struct adouble *ad, int fd)
+{
+	int rc = 0;
+	char *map = MAP_FAILED;
+	size_t origlen;
+
+	origlen = ad_getentryoff(ad, ADEID_RFORK) +
+		ad_getentrylen(ad, ADEID_RFORK);
+
+	/* FIXME: direct use of mmap(), vfs_aio_fork does it too */
+	map = mmap(NULL, origlen, PROT_WRITE, MAP_SHARED, fd, 0);
+	if (map == MAP_FAILED) {
+		DEBUG(2, ("mmap AppleDouble: %s\n", strerror(errno)));
+		rc = -1;
+		goto exit;
+	}
+
+	memmove(map + ad_getentryoff(ad, ADEID_FINDERI) + ADEDLEN_FINDERI,
+		map + ad_getentryoff(ad, ADEID_RFORK),
+		ad_getentrylen(ad, ADEID_RFORK));
+
+	ad_setentrylen(ad, ADEID_FINDERI, ADEDLEN_FINDERI);
+	ad_setentryoff(ad, ADEID_RFORK,
+		       ad_getentryoff(ad, ADEID_FINDERI) + ADEDLEN_FINDERI);
+
+	/*
+	 * FIXME: direct ftruncate(), but we don't have a fsp for the
+	 * VFS call
+	 */
+	rc = ftruncate(fd, ad_getentryoff(ad, ADEID_RFORK)
+		       + ad_getentrylen(ad, ADEID_RFORK));
+
+exit:
+	if (map != MAP_FAILED) {
+		munmap(map, origlen);
+	}
+	return rc;
+}
+
+/**
+ * Read and parse Netatalk AppleDouble metadata xattr
+ **/
+static ssize_t ad_header_read_meta(struct adouble *ad, const char *path)
+{
+	int      rc = 0;
+	ssize_t  ealen;
+	bool     ok;
+
+	DEBUG(10, ("reading meta xattr for %s\n", path));
+
+	ealen = SMB_VFS_GETXATTR(ad->ad_handle->conn, path,
+				 AFPINFO_EA_NETATALK, ad->ad_data,
+				 AD_DATASZ_XATTR);
+	if (ealen == -1) {
+		switch (errno) {
+		case ENOATTR:
+		case ENOENT:
+			if (errno == ENOATTR) {
+				errno = ENOENT;
+			}
+			rc = -1;
+			goto exit;
+		default:
+			DEBUG(2, ("error reading meta xattr: %s\n",
+				  strerror(errno)));
+			rc = -1;
+			goto exit;
+		}
+	}
+	if (ealen != AD_DATASZ_XATTR) {
+		DEBUG(2, ("bad size %zd\n", ealen));
+		errno = EINVAL;
+		rc = -1;
+		goto exit;
+	}
+
+	/* Now parse entries */
+	ok = ad_unpack(ad, ADEID_NUM_XATTR);
+	if (!ok) {
+		DEBUG(2, ("invalid AppleDouble metadata xattr\n"));
+		errno = EINVAL;
+		rc = -1;
+		goto exit;
+	}
+
+	if (!ad_getentryoff(ad, ADEID_FINDERI)
+	    || !ad_getentryoff(ad, ADEID_COMMENT)
+	    || !ad_getentryoff(ad, ADEID_FILEDATESI)
+	    || !ad_getentryoff(ad, ADEID_AFPFILEI)
+	    || !ad_getentryoff(ad, ADEID_PRIVDEV)
+	    || !ad_getentryoff(ad, ADEID_PRIVINO)
+	    || !ad_getentryoff(ad, ADEID_PRIVSYN)
+	    || !ad_getentryoff(ad, ADEID_PRIVID)) {
+		DEBUG(2, ("invalid AppleDouble metadata xattr\n"));
+		errno = EINVAL;
+		rc = -1;
+		goto exit;
+	}
+
+exit:
+	DEBUG(10, ("reading meta xattr for %s, rc: %d\n", path, rc));
+
+	if (rc != 0) {
+		ealen = -1;
+		if (errno == EINVAL) {
+			become_root();
+			removexattr(path, AFPINFO_EA_NETATALK);
+			unbecome_root();
+			errno = ENOENT;
+		}
+	}
+	return ealen;
+}
+
+/**
+ * Read and parse resource fork, either ._ AppleDouble file or xattr
+ **/
+static ssize_t ad_header_read_rsrc(struct adouble *ad, const char *path)
+{
+	struct fruit_config_data *config = NULL;
+	int fd = -1;
+	int rc = 0;
+	ssize_t len;
+	char *adpath = NULL;
+	bool opened = false;
+	int mode;
+	struct adouble *meta_ad = NULL;
+	SMB_STRUCT_STAT sbuf;
+	bool ok;
+	int saved_errno;
+
+	SMB_VFS_HANDLE_GET_DATA(ad->ad_handle, config,
+				struct fruit_config_data, return -1);
+
+	if (ad->ad_fsp && ad->ad_fsp->fh && (ad->ad_fsp->fh->fd != -1)) {
+		fd = ad->ad_fsp->fh->fd;
+	} else {
+		if (config->rsrc == FRUIT_RSRC_XATTR) {
+			adpath = talloc_strdup(talloc_tos(), path);
+		} else {
+			rc = adouble_path(talloc_tos(), path, &adpath);
+			if (rc != 0) {
+				goto exit;
+			}
+		}
+
+		/* Try rw first so we can use the fd in ad_convert() */
+		mode = O_RDWR;
+
+	retry:
+		if (config->rsrc == FRUIT_RSRC_XATTR) {
+#ifndef HAVE_ATTROPEN
+			errno = ENOSYS;
+			rc = -1;
+			goto exit;
+#else
+			/* FIXME: direct Solaris xattr syscall */
+			fd = attropen(adpath, AFPRESOURCE_EA_NETATALK,
+				      mode, 0);
+#endif
+		} else {
+			/* FIXME: direct open(), don't have an fsp */
+			fd = open(adpath, mode);
+		}
+
+		if (fd == -1) {
+			switch (errno) {
+			case EROFS:
+			case EACCES:
+				if (mode == O_RDWR) {
+					mode = O_RDONLY;
+					goto retry;
+				}
+				/* fall through ... */
+			default:
+				DEBUG(2, ("open AppleDouble: %s, %s\n",
+					  adpath, strerror(errno)));
+				rc = -1;
+				goto exit;
+			}
+		}
+		opened = true;
+	}
+
+	if (config->rsrc == FRUIT_RSRC_XATTR) {
+		/* FIXME: direct sys_fstat(), don't have an fsp */
+		rc = sys_fstat(
+			fd, &sbuf,
+			lp_fake_directory_create_times(
+				SNUM(ad->ad_handle->conn)));
+		if (rc != 0) {
+			rc = -1;
+			goto exit;
+		}
+		ad_setentrylen(ad, ADEID_RFORK, sbuf.st_ex_size);
+	} else {
+		/* FIXME: direct sys_pread(), don't have an fsp */
+		len = sys_pread(fd, ad->ad_data, AD_DATASZ_DOT_UND, 0);
+		if (len != AD_DATASZ_DOT_UND) {
+			DEBUG(2, ("%s: bad size: %zd\n",
+				  strerror(errno), len));
+			rc = -1;
+			goto exit;
+		}
+
+		/* Now parse entries */
+		ok = ad_unpack(ad, ADEID_NUM_DOT_UND);
+		if (!ok) {
+			DEBUG(1, ("invalid AppleDouble ressource %s\n", path));
+			errno = EINVAL;
+			rc = -1;
+			goto exit;
+		}
+
+		if ((ad_getentryoff(ad, ADEID_FINDERI)
+		     != ADEDOFF_FINDERI_DOT_UND)
+		    || (ad_getentrylen(ad, ADEID_FINDERI)
+			< ADEDLEN_FINDERI)
+		    || (ad_getentryoff(ad, ADEID_RFORK)
+			< ADEDOFF_RFORK_DOT_UND)) {
+			DEBUG(2, ("invalid AppleDouble ressource %s\n", path));
+			errno = EINVAL;
+			rc = -1;
+			goto exit;
+		}
+
+		if ((mode == O_RDWR)
+		    && (ad_getentrylen(ad, ADEID_FINDERI) > ADEDLEN_FINDERI)) {
+			rc = ad_convert(ad, fd);
+			if (rc != 0) {
+				rc = -1;
+				goto exit;
+			}
+			/*
+			 * Can't use ad_write() because we might not have a fsp
+			 */
+			rc = ad_pack(ad);
+			if (rc != 0) {
+				goto exit;
+			}
+			/* FIXME: direct sys_pwrite(), don't have an fsp */
+			len = sys_pwrite(fd, ad->ad_data,
+					 AD_DATASZ_DOT_UND, 0);
+			if (len != AD_DATASZ_DOT_UND) {
+				DEBUG(2, ("%s: bad size: %zd\n", adpath, len));
+				rc = -1;
+				goto exit;
+			}
+
+			meta_ad = ad_init(talloc_tos(), ad->ad_handle,
+					  ADOUBLE_META, NULL);
+			if (meta_ad == NULL) {
+				rc = -1;
+				goto exit;
+			}
+
+			memcpy(ad_entry(meta_ad, ADEID_FINDERI),
+			       ad_entry(ad, ADEID_FINDERI),
+			       ADEDLEN_FINDERI);
+
+			rc = ad_write(meta_ad, path);
+			if (rc != 0) {
+				rc = -1;
+				goto exit;
+			}
+		}
+	}
+
+	DEBUG(10, ("opened AppleDouble: %s\n", path));
+
+exit:
+	if (rc != 0) {
+		saved_errno = errno;
+		len = -1;
+	}
+	if (opened && fd != -1) {
+		close(fd);
+	}
+	TALLOC_FREE(adpath);
+	TALLOC_FREE(meta_ad);
+	if (rc != 0) {
+		errno = saved_errno;
+	}
+	return len;
+}
+
+/**
+ * Read and unpack an AppleDouble metadata xattr or resource
+ **/
+static ssize_t ad_read(struct adouble *ad, const char *path)
+{
+	switch (ad->ad_type) {
+	case ADOUBLE_META:
+		return ad_header_read_meta(ad, path);
+	case ADOUBLE_RSRC:
+		return ad_header_read_rsrc(ad, path);
+	default:
+		return -1;
+	}
+}
+
+/**
+ * Allocate a struct adouble without initialiing it
+ *
+ * The struct is either hang of the fsp extension context or if fsp is
+ * NULL from ctx.
+ *
+ * @param[in] ctx        talloc context
+ * @param[in] handle     vfs handle
+ * @param[in] type       type of AppleDouble, ADOUBLE_META or ADOUBLE_RSRC
+
+ * @param[in] fsp        if not NULL (for stream IO), the adouble handle is
+ *                       added as an fsp extension
+ *
+ * @return               adouble handle
+ **/
+static struct adouble *ad_alloc(TALLOC_CTX *ctx, vfs_handle_struct *handle,
+				adouble_type_t type, files_struct *fsp)
+{
+	int rc = 0;
+	size_t adsize = 0;
+	struct adouble *ad;
+	struct fruit_config_data *config;
+
+	SMB_VFS_HANDLE_GET_DATA(handle, config,
+				struct fruit_config_data, return NULL);
+
+	switch (type) {
+	case ADOUBLE_META:
+		adsize = AD_DATASZ_XATTR;
+		break;
+	case ADOUBLE_RSRC:
+		if (config->rsrc == FRUIT_RSRC_ADFILE) {
+			adsize = AD_DATASZ_DOT_UND;
+		}
+		break;
+	default:
+		return NULL;
+	}
+
+	if (!fsp) {
+		ad = talloc_zero(ctx, struct adouble);
+		if (ad == NULL) {
+			rc = -1;
+			goto exit;
+		}
+		if (adsize) {
+			ad->ad_data = talloc_zero_array(ad, char, adsize);
+		}
+	} else {
+		ad = (struct adouble *)VFS_ADD_FSP_EXTENSION(handle, fsp,
+							     struct adouble,
+							     NULL);
+		if (ad == NULL) {
+			rc = -1;
+			goto exit;
+		}
+		if (adsize) {
+			ad->ad_data = talloc_zero_array(
+				VFS_MEMCTX_FSP_EXTENSION(handle, fsp),
+				char, adsize);
+		}
+		ad->ad_fsp = fsp;
+	}
+
+	if (adsize && ad->ad_data == NULL) {
+		rc = -1;
+		goto exit;
+	}
+	ad->ad_handle = handle;
+	ad->ad_type = type;
+	ad->ad_magic = AD_MAGIC;
+	ad->ad_version = AD_VERSION;
+
+exit:
+	if (rc != 0) {
+		TALLOC_FREE(ad);
+	}
+	return ad;
+}
+
+/**
+ * Allocate and initialize a new struct adouble
+ *
+ * @param[in] ctx        talloc context
+ * @param[in] handle     vfs handle
+ * @param[in] type       type of AppleDouble, ADOUBLE_META or ADOUBLE_RSRC
+ * @param[in] fsp        file handle, may be NULL for a type of e_ad_meta
+ *
+ * @return               adouble handle, initialized
+ **/
+static struct adouble *ad_init(TALLOC_CTX *ctx, vfs_handle_struct *handle,
+			       adouble_type_t type, files_struct *fsp)
+{
+	int rc = 0;
+	const struct ad_entry_order  *eid;
+	struct adouble *ad = NULL;
+	struct fruit_config_data *config;
+	time_t t = time(NULL);
+
+	SMB_VFS_HANDLE_GET_DATA(handle, config,
+				struct fruit_config_data, return NULL);
+
+	switch (type) {
+	case ADOUBLE_META:
+		eid = entry_order_meta_xattr;
+		break;
+	case ADOUBLE_RSRC:
+		if (config->rsrc == FRUIT_RSRC_ADFILE) {
+			eid = entry_order_dot_und;
+		} else {
+			eid = entry_order_rsrc_xattr;
+		}
+		break;
+	default:
+		return NULL;
+	}
+
+	ad = ad_alloc(ctx, handle, type, fsp);
+	if (ad == NULL) {
+		return NULL;
+	}
+
+	while (eid->id) {
+		ad->ad_eid[eid->id].ade_off = eid->offset;
+		ad->ad_eid[eid->id].ade_len = eid->len;
+		eid++;
+	}
+
+	/* put something sane in the date fields */
+	ad_setdate(ad, AD_DATE_CREATE | AD_DATE_UNIX, t);
+	ad_setdate(ad, AD_DATE_MODIFY | AD_DATE_UNIX, t);
+	ad_setdate(ad, AD_DATE_ACCESS | AD_DATE_UNIX, t);
+	ad_setdate(ad, AD_DATE_BACKUP, htonl(AD_DATE_START));
+
+	if (rc != 0) {
+		TALLOC_FREE(ad);
+	}
+	return ad;
+}
+
+/**
+ * Return AppleDouble data for a file
+ *
+ * @param[in] ctx      talloc context
+ * @param[in] handle   vfs handle
+ * @param[in] path     pathname to file or directory
+ * @param[in] type     type of AppleDouble, ADOUBLE_META or ADOUBLE_RSRC
+ *
+ * @return             talloced struct adouble or NULL on error
+ **/
+static struct adouble *ad_get(TALLOC_CTX *ctx, vfs_handle_struct *handle,
+			      const char *path, adouble_type_t type)
+{
+	int rc = 0;
+	ssize_t len;
+	struct adouble *ad = NULL;
+
+	DEBUG(10, ("ad_get(%s) called for %s\n",
+		   type == ADOUBLE_META ? "meta" : "rsrc", path));
+
+	ad = ad_alloc(ctx, handle, type, NULL);
+	if (ad == NULL) {
+		rc = -1;
+		goto exit;
+	}
+
+	len = ad_read(ad, path);
+	if (len == -1) {
+		DEBUG(10, ("error reading AppleDouble for %s\n", path));
+		rc = -1;
+		goto exit;
+	}
+
+exit:
+	DEBUG(10, ("ad_get(%s) for %s returning %d\n",
+		  type == ADOUBLE_META ? "meta" : "rsrc", path, rc));
+
+	if (rc != 0) {
+		TALLOC_FREE(ad);
+	}
+	return ad;
+}
+
+/**
+ * Set AppleDouble metadata on a file or directory
+ *
+ * @param[in] ad      adouble handle
+ * @param[in] path    pathname to file or directory
+ *
+ * @return            status code, 0 means success
+ **/
+static int ad_write(struct adouble *ad, const char *path)
+{
+	int rc = 0;
+	ssize_t len;
+
+	rc = ad_pack(ad);
+	if (rc != 0) {
+		goto exit;
+	}
+
+	switch (ad->ad_type) {
+	case ADOUBLE_META:
+		rc = SMB_VFS_SETXATTR(ad->ad_handle->conn, path,
+				      AFPINFO_EA_NETATALK, ad->ad_data,
+				      AD_DATASZ_XATTR, 0);
+		break;
+	case ADOUBLE_RSRC:
+		if ((ad->ad_fsp == NULL)
+		    || (ad->ad_fsp->fh == NULL)
+		    || (ad->ad_fsp->fh->fd == -1)) {
+			rc = -1;
+			goto exit;
+		}
+		/* FIXME: direct sys_pwrite(), don't have an fsp */
+		len = sys_pwrite(ad->ad_fsp->fh->fd, ad->ad_data,
+				 talloc_get_size(ad->ad_data), 0);
+		if (len != talloc_get_size(ad->ad_data)) {
+			DEBUG(1, ("short write on %s: %zd", path, len));
+			rc = -1;
+			goto exit;
+		}
+		break;
+	default:
+		return -1;
+	}
+exit:
+	return rc;
+}
+
+/*****************************************************************************
+ * Helper functions
+ *****************************************************************************/
+
+static bool is_afpinfo_stream(const struct smb_filename *smb_fname)
+{
+	if (strncasecmp_m(smb_fname->stream_name,
+			  AFPINFO_STREAM_NAME,
+			  strlen(AFPINFO_STREAM_NAME)) == 0) {
+		return true;
+	}
+	return false;
+}
+
+static bool is_afpresource_stream(const struct smb_filename *smb_fname)
+{
+	if (strncasecmp_m(smb_fname->stream_name,
+			  AFPRESOURCE_STREAM_NAME,
+			  strlen(AFPRESOURCE_STREAM_NAME)) == 0) {
+		return true;
+	}
+	return false;
+}
+
+/**
+ * Test whether stream is an Apple stream, not used atm
+ **/
+#if 0
+static bool is_apple_stream(const struct smb_filename *smb_fname)
+{
+	if (is_afpinfo_stream(smb_fname)) {
+		return true;
+	}
+	if (is_afpresource_stream(smb_fname)) {
+		return true;
+	}
+	return false;
+}
+#endif
+
+/**
+ * Initialize config struct from our smb.conf config parameters
+ **/
+static int init_fruit_config(vfs_handle_struct *handle)
+{
+	struct fruit_config_data *config;
+	int enumval;
+
+	config = talloc_zero(handle->conn, struct fruit_config_data);
+	if (!config) {
+		DEBUG(1, ("talloc_zero() failed\n"));
+		errno = ENOMEM;
+		return -1;
+	}
+
+	enumval = lp_parm_enum(SNUM(handle->conn), FRUIT_PARAM_TYPE_NAME,
+			       "ressource", fruit_rsrc, FRUIT_RSRC_ADFILE);
+	if (enumval == -1) {
+		DEBUG(1, ("value for %s: ressource type unknown\n",
+			  FRUIT_PARAM_TYPE_NAME));
+		return -1;
+	}
+	config->rsrc = (enum fruit_rsrc)enumval;
+
+	enumval = lp_parm_enum(SNUM(handle->conn), FRUIT_PARAM_TYPE_NAME,
+			       "metadata", fruit_meta, FRUIT_META_NETATALK);
+	if (enumval == -1) {
+		DEBUG(1, ("value for %s: metadata type unknown\n",
+			  FRUIT_PARAM_TYPE_NAME));
+		return -1;
+	}
+	config->meta = (enum fruit_meta)enumval;
+
+	enumval = lp_parm_enum(SNUM(handle->conn), FRUIT_PARAM_TYPE_NAME,
+			       "locking", fruit_locking, FRUIT_LOCKING_NONE);
+	if (enumval == -1) {
+		DEBUG(1, ("value for %s: locking type unknown\n",
+			  FRUIT_PARAM_TYPE_NAME));
+		return -1;
+	}
+	config->locking = (enum fruit_locking)enumval;
+
+	enumval = lp_parm_enum(SNUM(handle->conn), FRUIT_PARAM_TYPE_NAME,
+			       "encoding", fruit_encoding, FRUIT_ENC_PRIVATE);
+	if (enumval == -1) {
+		DEBUG(1, ("value for %s: encoding type unknown\n",
+			  FRUIT_PARAM_TYPE_NAME));
+		return -1;
+	}
+	config->encoding = (enum fruit_encoding)enumval;
+
+	SMB_VFS_HANDLE_SET_DATA(handle, config,
+				NULL, struct fruit_config_data,
+				return -1);
+
+	return 0;
+}
+
+/**
+ * Prepend "._" to a basename
+ **/
+static int adouble_path(TALLOC_CTX *ctx, const char *path_in, char **path_out)
+{
+	char *parent;
+	const char *basename;
+
+	if (!parent_dirname(ctx, path_in, &parent, &basename)) {
+		return -1;
+	}
+
+	*path_out = talloc_asprintf(ctx, "%s/._%s", parent, basename);
+	if (*path_out == NULL) {
+		return -1;
+	}
+
+	return 0;
+}
+
+/**
+ * Allocate and initialize an AfpInfo struct
+ **/
+static AfpInfo *afpinfo_new(TALLOC_CTX *ctx)
+{
+	AfpInfo *ai = talloc_zero(ctx, AfpInfo);
+	if (ai == NULL) {
+		return NULL;
+	}
+	ai->afpi_Signature = AFP_Signature;
+	ai->afpi_Version = AFP_Version;
+	ai->afpi_BackupTime = AD_DATE_START;
+	return ai;
+}
+
+/**
+ * Pack an AfpInfo struct into a buffer
+ *
+ * Buffer size must be at least AFP_INFO_SIZE
+ * Returns size of packed buffer
+ **/
+static ssize_t afpinfo_pack(const AfpInfo *ai, char *buf)
+{
+	memset(buf, 0, AFP_INFO_SIZE);
+
+	RSIVAL(buf, 0, ai->afpi_Signature);
+	RSIVAL(buf, 4, ai->afpi_Version);
+	RSIVAL(buf, 12, ai->afpi_BackupTime);
+	memcpy(buf + 16, ai->afpi_FinderInfo, sizeof(ai->afpi_FinderInfo));
+
+	return AFP_INFO_SIZE;
+}
+
+/**
+ * Unpack a buffer into a AfpInfo structure
+ *
+ * Buffer size must be at least AFP_INFO_SIZE
+ * Returns allocated AfpInfo struct
+ **/
+static AfpInfo *afpinfo_unpack(TALLOC_CTX *ctx, const void *data)
+{
+	AfpInfo *ai = talloc_zero(ctx, AfpInfo);
+	if (ai == NULL) {
+		return NULL;
+	}
+
+	ai->afpi_Signature = RIVAL(data, 0);
+	ai->afpi_Version = RIVAL(data, 4);
+	ai->afpi_BackupTime = RIVAL(data, 12);
+	memcpy(ai->afpi_FinderInfo, (const char *)data + 16,
+	       sizeof(ai->afpi_FinderInfo));
+
+	if (ai->afpi_Signature != AFP_Signature
+	    || ai->afpi_Version != AFP_Version) {
+		DEBUG(1, ("Bad AfpInfo signature or version\n"));
+		TALLOC_FREE(ai);
+	}
+
+	return ai;
+}
+
+/**
+ * Fake an inode number from the md5 hash of the (xattr) name
+ **/
+static SMB_INO_T fruit_inode(const SMB_STRUCT_STAT *sbuf, const char *sname)
+{
+	MD5_CTX ctx;
+	unsigned char hash[16];
+	SMB_INO_T result;
+	char *upper_sname;
+
+	upper_sname = talloc_strdup_upper(talloc_tos(), sname);
+	SMB_ASSERT(upper_sname != NULL);
+
+	MD5Init(&ctx);
+	MD5Update(&ctx, (const unsigned char *)&(sbuf->st_ex_dev),
+		  sizeof(sbuf->st_ex_dev));
+	MD5Update(&ctx, (const unsigned char *)&(sbuf->st_ex_ino),
+		  sizeof(sbuf->st_ex_ino));
+	MD5Update(&ctx, (unsigned char *)upper_sname,
+		  talloc_get_size(upper_sname)-1);
+	MD5Final(hash, &ctx);
+
+	TALLOC_FREE(upper_sname);
+
+	/* Hopefully all the variation is in the lower 4 (or 8) bytes! */
+	memcpy(&result, hash, sizeof(result));
+
+	DEBUG(10, ("fruit_inode \"%s\": ino=0x%llu\n",
+		   sname, (unsigned long long)result));
+
+	return result;
+}
+
+/**
+ * Ensure ad_fsp is still valid
+ **/
+static bool fruit_fsp_recheck(struct adouble *ad, files_struct *fsp)
+{
+	if (ad->ad_fsp == fsp) {
+		return true;
+	}
+	ad->ad_fsp = fsp;
+
+	return true;
+}
+
+static bool add_fruit_stream(TALLOC_CTX *mem_ctx, unsigned int *num_streams,
+			     struct stream_struct **streams,
+			     const char *name, off_t size,
+			     off_t alloc_size)
+{
+	struct stream_struct *tmp;
+
+	tmp = talloc_realloc(mem_ctx, *streams, struct stream_struct,
+			     (*num_streams)+1);
+	if (tmp == NULL) {
+		return false;
+	}
+
+	tmp[*num_streams].name = talloc_asprintf(tmp, "%s:$DATA", name);
+	if (tmp[*num_streams].name == NULL) {
+		return false;
+	}
+
+	tmp[*num_streams].size = size;
+	tmp[*num_streams].alloc_size = alloc_size;
+
+	*streams = tmp;
+	*num_streams += 1;
+	return true;
+}
+
+static bool empty_finderinfo(const struct adouble *ad)
+{
+
+	char emptybuf[ADEDLEN_FINDERI] = {0};
+	if (memcmp(emptybuf,
+		   ad_entry(ad, ADEID_FINDERI),
+		   ADEDLEN_FINDERI) == 0) {
+		return true;
+	}
+	return false;
+}
+
+/**
+ * Update btime with btime from Netatalk
+ **/
+static void update_btime(vfs_handle_struct *handle,
+			 struct smb_filename *smb_fname)
+{
+	uint32_t t;
+	struct timespec creation_time = {0};
+	struct adouble *ad;
+
+	ad = ad_get(talloc_tos(), handle, smb_fname->base_name, ADOUBLE_META);
+	if (ad == NULL) {
+		return;
+	}
+	if (ad_getdate(ad, AD_DATE_UNIX | AD_DATE_CREATE, &t) != 0) {
+		TALLOC_FREE(ad);
+		return;
+	}
+	TALLOC_FREE(ad);
+
+	creation_time.tv_sec = convert_uint32_t_to_time_t(t);
+	update_stat_ex_create_time(&smb_fname->st, creation_time);
+
+	return;
+}
+
+/**
+ * Map an access mask to a Netatalk single byte byte range lock
+ **/
+static off_t access_to_netatalk_brl(enum apple_fork fork,
+				    uint32_t access_mask)
+{
+	off_t offset;
+
+	switch (access_mask) {
+	case FILE_READ_DATA:
+		offset = AD_FILELOCK_OPEN_RD;
+		break;
+
+	case FILE_WRITE_DATA:
+	case FILE_APPEND_DATA:
+		offset = AD_FILELOCK_OPEN_WR;
+		break;
+
+	default:
+		offset = AD_FILELOCK_OPEN_NONE;
+		break;
+	}
+
+	if (fork == APPLE_FORK_RSRC) {
+		if (offset == AD_FILELOCK_OPEN_NONE) {
+			offset = AD_FILELOCK_RSRC_OPEN_NONE;
+		} else {
+			offset += 2;
+		}
+	}
+
+	return offset;
+}
+
+/**
+ * Map a deny mode to a Netatalk brl
+ **/
+static off_t denymode_to_netatalk_brl(enum apple_fork fork,
+				      uint32_t deny_mode)
+{
+	off_t offset;
+
+	switch (deny_mode) {
+	case DENY_READ:
+		offset = AD_FILELOCK_DENY_RD;
+		break;
+
+	case DENY_WRITE:
+		offset = AD_FILELOCK_DENY_WR;
+		break;
+
+	default:
+		smb_panic("denymode_to_netatalk_brl: bad deny mode\n");
+	}
+
+	if (fork == APPLE_FORK_RSRC) {
+		offset += 2;
+	}
+
+	return offset;
+}
+
+/**
+ * Call fcntl() with an exclusive F_GETLK request in order to
+ * determine if there's an exisiting shared lock
+ *
+ * @return true if the requested lock was found or any error occured
+ *         false if the lock was not found
+ **/
+static bool test_netatalk_lock(files_struct *fsp, off_t in_offset)
+{
+	bool result;
+	off_t offset = in_offset;
+	off_t len = 1;
+	int type = F_WRLCK;
+	pid_t pid;
+
+	result = SMB_VFS_GETLOCK(fsp, &offset, &len, &type, &pid);
+	if (result == false) {
+		return true;
+	}
+
+	if (type != F_UNLCK) {
+		return true;
+	}
+
+	return false;
+}
+
+static NTSTATUS fruit_check_access(vfs_handle_struct *handle,
+				   files_struct *fsp,
+				   uint32_t access_mask,
+				   uint32_t deny_mode)
+{
+	NTSTATUS status = NT_STATUS_OK;
+	struct byte_range_lock *br_lck = NULL;
+	bool open_for_reading, open_for_writing, deny_read, deny_write;
+	off_t off;
+
+	/* FIXME: hardcoded data fork, add resource fork */
+	enum apple_fork fork = APPLE_FORK_DATA;
+
+	DEBUG(10, ("fruit_check_access: %s, am: %s/%s, dm: %s/%s\n",
+		  fsp_str_dbg(fsp),
+		  access_mask & FILE_READ_DATA ? "READ" :"-",
+		  access_mask & FILE_WRITE_DATA ? "WRITE" : "-",
+		  deny_mode & DENY_READ ? "DENY_READ" : "-",
+		  deny_mode & DENY_WRITE ? "DENY_WRITE" : "-"));
+
+	/*
+	 * Check read access and deny read mode
+	 */
+	if ((access_mask & FILE_READ_DATA) || (deny_mode & DENY_READ)) {
+		/* Check access */
+		open_for_reading = test_netatalk_lock(
+			fsp, access_to_netatalk_brl(fork, FILE_READ_DATA));
+
+		deny_read = test_netatalk_lock(
+			fsp, denymode_to_netatalk_brl(fork, DENY_READ));
+
+		DEBUG(10, ("read: %s, deny_write: %s\n",
+			  open_for_reading == true ? "yes" : "no",
+			  deny_read == true ? "yes" : "no"));
+
+		if (((access_mask & FILE_READ_DATA) && deny_read)
+		    || ((deny_mode & DENY_READ) && open_for_reading)) {
+			return NT_STATUS_SHARING_VIOLATION;
+		}
+
+		/* Set locks */
+		if (access_mask & FILE_READ_DATA) {
+			off = access_to_netatalk_brl(fork, FILE_READ_DATA);
+			br_lck = do_lock(
+				handle->conn->sconn->msg_ctx, fsp,
+				fsp->op->global->open_persistent_id, 1, off,
+				READ_LOCK, POSIX_LOCK, false,
+				&status, NULL);
+
+			if (!NT_STATUS_IS_OK(status))  {
+				return status;
+			}
+			TALLOC_FREE(br_lck);
+		}
+
+		if (deny_mode & DENY_READ) {
+			off = denymode_to_netatalk_brl(fork, DENY_READ);
+			br_lck = do_lock(
+				handle->conn->sconn->msg_ctx, fsp,
+				fsp->op->global->open_persistent_id, 1, off,
+				READ_LOCK, POSIX_LOCK, false,
+				&status, NULL);
+
+			if (!NT_STATUS_IS_OK(status)) {
+				return status;
+			}
+			TALLOC_FREE(br_lck);
+		}
+	}
+
+	/*
+	 * Check write access and deny write mode
+	 */
+	if ((access_mask & FILE_WRITE_DATA) || (deny_mode & DENY_WRITE)) {
+		/* Check access */
+		open_for_writing = test_netatalk_lock(
+			fsp, access_to_netatalk_brl(fork, FILE_WRITE_DATA));
+
+		deny_write = test_netatalk_lock(
+			fsp, denymode_to_netatalk_brl(fork, DENY_WRITE));
+
+		DEBUG(10, ("write: %s, deny_write: %s\n",
+			  open_for_writing == true ? "yes" : "no",
+			  deny_write == true ? "yes" : "no"));
+
+		if (((access_mask & FILE_WRITE_DATA) && deny_write)
+		    || ((deny_mode & DENY_WRITE) && open_for_writing)) {
+			return NT_STATUS_SHARING_VIOLATION;
+		}
+
+		/* Set locks */
+		if (access_mask & FILE_WRITE_DATA) {
+			off = access_to_netatalk_brl(fork, FILE_WRITE_DATA);
+			br_lck = do_lock(
+				handle->conn->sconn->msg_ctx, fsp,
+				fsp->op->global->open_persistent_id, 1, off,
+				READ_LOCK, POSIX_LOCK, false,
+				&status, NULL);
+
+			if (!NT_STATUS_IS_OK(status)) {
+				return status;
+			}
+			TALLOC_FREE(br_lck);
+
+		}
+		if (deny_mode & DENY_WRITE) {
+			off = denymode_to_netatalk_brl(fork, DENY_WRITE);
+			br_lck = do_lock(
+				handle->conn->sconn->msg_ctx, fsp,
+				fsp->op->global->open_persistent_id, 1, off,
+				READ_LOCK, POSIX_LOCK, false,
+				&status, NULL);
+
+			if (!NT_STATUS_IS_OK(status)) {
+				return status;
+			}
+			TALLOC_FREE(br_lck);
+		}
+	}
+
+	TALLOC_FREE(br_lck);
+
+	return status;
+}
+
+/****************************************************************************
+ * VFS ops
+ ****************************************************************************/
+
+static int fruit_connect(vfs_handle_struct *handle,
+			 const char *service,
+			 const char *user)
+{
+	int rc;
+	char *list = NULL, *newlist = NULL;
+	struct fruit_config_data *config;
+
+	DEBUG(10, ("fruit_connect\n"));
+
+	rc = SMB_VFS_NEXT_CONNECT(handle, service, user);
+	if (rc < 0) {
+		return rc;
+	}
+
+	list = lp_veto_files(talloc_tos(), SNUM(handle->conn));
+
+	if (list) {
+		if (strstr(list, "/" ADOUBLE_NAME_PREFIX "*/") == NULL) {
+			newlist = talloc_asprintf(
+				list,
+				"%s/" ADOUBLE_NAME_PREFIX "*/",
+				list);
+			lp_do_parameter(SNUM(handle->conn),
+					"veto files",
+					newlist);
+		}
+	} else {
+		lp_do_parameter(SNUM(handle->conn),
+				"veto files",
+				"/" ADOUBLE_NAME_PREFIX "*/");
+	}
+
+	TALLOC_FREE(list);
+	TALLOC_FREE(newlist);
+
+	rc = init_fruit_config(handle);
+	if (rc != 0) {
+		return rc;
+	}
+
+	SMB_VFS_HANDLE_GET_DATA(handle, config,
+				struct fruit_config_data, return -1);
+
+	if (config->encoding == FRUIT_ENC_NATIVE) {
+		lp_do_parameter(
+			SNUM(handle->conn),
+			"catia:mappings",
+			"0x22:0xf020,0x2a:0xf021,0x3a:0xf022,0x3c:0xf023,"
+			"0x3e:0xf024,0x3f:0xf025,0x5c:0xf026,0x7c:0xf027,"
+			"0x0d:0xf00d");
+	}
+
+	return rc;
+}
+
+static int fruit_open_meta(vfs_handle_struct *handle,
+			   struct smb_filename *smb_fname,
+			   files_struct *fsp, int flags, mode_t mode)
+{
+	int rc = 0;
+	struct fruit_config_data *config = NULL;
+	struct smb_filename *smb_fname_base = NULL;
+	int baseflags;
+	int hostfd = -1;
+	struct adouble *ad = NULL;
+
+	DEBUG(10, ("fruit_open_meta for %s\n", smb_fname_str_dbg(smb_fname)));
+
+	SMB_VFS_HANDLE_GET_DATA(handle, config,
+				struct fruit_config_data, return -1);
+
+	if (config->meta == FRUIT_META_STREAM) {
+		return SMB_VFS_NEXT_OPEN(handle, smb_fname, fsp, flags, mode);
+	}
+
+	/* Create an smb_filename with stream_name == NULL. */
+	smb_fname_base = synthetic_smb_fname(talloc_tos(),
+					     smb_fname->base_name, NULL, NULL);
+
+	if (smb_fname_base == NULL) {
+		errno = ENOMEM;
+		rc = -1;
+		goto exit;
+	}
+
+	/*
+	 * We use baseflags to turn off nasty side-effects when opening the
+	 * underlying file.
+	 */
+	baseflags = flags;
+	baseflags &= ~O_TRUNC;
+	baseflags &= ~O_EXCL;
+	baseflags &= ~O_CREAT;
+
+	hostfd = SMB_VFS_OPEN(handle->conn, smb_fname_base, fsp,
+			      baseflags, mode);
+
+	/*
+	 * It is legit to open a stream on a directory, but the base
+	 * fd has to be read-only.
+	 */
+	if ((hostfd == -1) && (errno == EISDIR)) {
+		baseflags &= ~O_ACCMODE;
+		baseflags |= O_RDONLY;
+		hostfd = SMB_VFS_OPEN(handle->conn, smb_fname_base, fsp,
+				      baseflags, mode);
+	}
+
+	TALLOC_FREE(smb_fname_base);
+
+	if (hostfd == -1) {
+		rc = -1;
+		goto exit;
+	}
+
+	if (flags & (O_CREAT | O_TRUNC)) {
+		/*
+		 * The attribute does not exist or needs to be truncated,
+		 * create an AppleDouble EA
+		 */
+		ad = ad_init(VFS_MEMCTX_FSP_EXTENSION(handle, fsp),
+			     handle, ADOUBLE_META, fsp);
+		if (ad == NULL) {
+			rc = -1;
+			goto exit;
+		}
+
+		rc = ad_write(ad, smb_fname->base_name);
+		if (rc != 0) {
+			rc = -1;
+			goto exit;
+		}
+	} else {
+		ad = ad_alloc(VFS_MEMCTX_FSP_EXTENSION(handle, fsp),
+			      handle, ADOUBLE_META, fsp);
+		if (ad == NULL) {
+			rc = -1;
+			goto exit;
+		}
+		if (ad_read(ad, smb_fname->base_name) == -1) {
+			rc = -1;
+			goto exit;
+		}
+	}
+
+exit:
+	DEBUG(10, ("fruit_open meta rc=%d, fd=%d\n", rc, hostfd));
+	if (rc != 0) {
+		int saved_errno = errno;
+		if (hostfd >= 0) {
+			/*
+			 * BUGBUGBUG -- we would need to call
+			 * fd_close_posix here, but we don't have a
+			 * full fsp yet
+			 */
+			fsp->fh->fd = hostfd;
+			SMB_VFS_CLOSE(fsp);
+		}
+		hostfd = -1;
+		errno = saved_errno;
+	}
+	return hostfd;
+}
+
+static int fruit_open_rsrc(vfs_handle_struct *handle,
+			   struct smb_filename *smb_fname,
+			   files_struct *fsp, int flags, mode_t mode)
+{
+	int rc = 0;
+	struct fruit_config_data *config = NULL;
+	struct adouble *ad = NULL;
+	struct smb_filename *smb_fname_base = NULL;
+	char *adpath = NULL;
+	int hostfd = -1;
+
+	DEBUG(10, ("fruit_open_rsrc for %s\n", smb_fname_str_dbg(smb_fname)));
+
+	SMB_VFS_HANDLE_GET_DATA(handle, config,
+				struct fruit_config_data, return -1);
+
+	switch (config->rsrc) {
+	case FRUIT_RSRC_STREAM:
+		return SMB_VFS_NEXT_OPEN(handle, smb_fname, fsp, flags, mode);
+	case FRUIT_RSRC_XATTR:
+#ifdef HAVE_ATTROPEN
+		hostfd = attropen(smb_fname->base_name,
+				  AFPRESOURCE_EA_NETATALK, flags, mode);
+		if (hostfd == -1) {
+			return -1;
+		}
+		ad = ad_init(VFS_MEMCTX_FSP_EXTENSION(handle, fsp),
+			     handle, ADOUBLE_RSRC, fsp);
+		if (ad == NULL) {
+			rc = -1;
+			goto exit;
+		}
+		goto exit;
+#else
+		errno = ENOTSUP;
+		return -1;
+#endif
+	default:
+		break;
+	}
+
+	if (!(flags & O_CREAT) && !VALID_STAT(smb_fname->st)) {
+		rc = SMB_VFS_NEXT_STAT(handle, smb_fname);
+		if (rc != 0) {
+			rc = -1;
+			goto exit;
+		}
+	}
+
+	if (VALID_STAT(smb_fname->st) && S_ISDIR(smb_fname->st.st_ex_mode)) {
+		/* sorry, but directories don't habe a resource fork */
+		rc = -1;
+		goto exit;
+	}
+
+	rc = adouble_path(talloc_tos(), smb_fname->base_name, &adpath);
+	if (rc != 0) {
+		goto exit;
+	}
+
+	/* Create an smb_filename with stream_name == NULL. */
+	smb_fname_base = synthetic_smb_fname(talloc_tos(),
+					     adpath, NULL, NULL);
+	if (smb_fname_base == NULL) {
+		errno = ENOMEM;
+		rc = -1;
+		goto exit;
+	}
+
+	/* Sanitize flags */
+	if (flags & O_WRONLY) {
+		/* We always need read access for the metadata header too */
+		flags &= ~O_WRONLY;
+		flags |= O_RDWR;
+	}
+
+	hostfd = SMB_VFS_OPEN(handle->conn, smb_fname_base, fsp,
+			      flags, mode);
+	if (hostfd == -1) {
+		rc = -1;
+		goto exit;
+	}
+
+	/* REVIEW: we need this in ad_write() */
+	fsp->fh->fd = hostfd;
+
+	if (flags & (O_CREAT | O_TRUNC)) {
+		ad = ad_init(VFS_MEMCTX_FSP_EXTENSION(handle, fsp),
+			     handle, ADOUBLE_RSRC, fsp);
+		if (ad == NULL) {
+			rc = -1;
+			goto exit;
+		}
+		rc = ad_write(ad, smb_fname->base_name);
+		if (rc != 0) {
+			rc = -1;
+			goto exit;
+		}
+	} else {
+		ad = ad_alloc(VFS_MEMCTX_FSP_EXTENSION(handle, fsp),
+			      handle, ADOUBLE_RSRC, fsp);
+		if (ad == NULL) {
+			rc = -1;
+			goto exit;
+		}
+		if (ad_read(ad, smb_fname->base_name) == -1) {
+			rc = -1;
+			goto exit;
+		}
+	}
+
+exit:
+
+	TALLOC_FREE(adpath);
+	TALLOC_FREE(smb_fname_base);
+
+	DEBUG(10, ("fruit_open resource fork: rc=%d, fd=%d\n", rc, hostfd));
+	if (rc != 0) {
+		int saved_errno = errno;
+		if (hostfd >= 0) {
+			/*
+			 * BUGBUGBUG -- we would need to call
+			 * fd_close_posix here, but we don't have a
+			 * full fsp yet
+			 */
+			fsp->fh->fd = hostfd;
+			SMB_VFS_CLOSE(fsp);
+		}
+		hostfd = -1;
+		errno = saved_errno;
+	}
+	return hostfd;
+}
+
+static int fruit_open(vfs_handle_struct *handle,
+                      struct smb_filename *smb_fname,
+                      files_struct *fsp, int flags, mode_t mode)
+{
+	DEBUG(10, ("fruit_open called for %s\n",
+		   smb_fname_str_dbg(smb_fname)));
+
+	if (!is_ntfs_stream_smb_fname(smb_fname)) {
+		return SMB_VFS_NEXT_OPEN(handle, smb_fname, fsp, flags, mode);
+	}
+
+	if (is_afpinfo_stream(smb_fname)) {
+		return fruit_open_meta(handle, smb_fname, fsp, flags, mode);
+	} else if (is_afpresource_stream(smb_fname)) {
+		return fruit_open_rsrc(handle, smb_fname, fsp, flags, mode);
+	}
+
+	return SMB_VFS_NEXT_OPEN(handle, smb_fname, fsp, flags, mode);
+}
+
+static int fruit_rename(struct vfs_handle_struct *handle,
+			const struct smb_filename *smb_fname_src,
+			const struct smb_filename *smb_fname_dst)
+{
+	int rc = -1;
+	char *src_adouble_path = NULL;
+	char *dst_adouble_path = NULL;
+	struct fruit_config_data *config = NULL;
+
+	rc = SMB_VFS_NEXT_RENAME(handle, smb_fname_src, smb_fname_dst);
+
+	if (!VALID_STAT(smb_fname_src->st)
+	    || !S_ISREG(smb_fname_src->st.st_ex_mode)) {
+		return rc;
+	}
+
+	SMB_VFS_HANDLE_GET_DATA(handle, config,
+				struct fruit_config_data, return -1);
+
+	if (config->rsrc == FRUIT_RSRC_XATTR) {
+		return rc;
+	}
+
+	rc = adouble_path(talloc_tos(), smb_fname_src->base_name,
+			  &src_adouble_path);
+	if (rc != 0) {
+		goto done;
+	}
+	rc = adouble_path(talloc_tos(), smb_fname_dst->base_name,
+			  &dst_adouble_path);
+	if (rc != 0) {
+		goto done;
+	}
+
+	DEBUG(10, ("fruit_rename: %s -> %s\n",
+		   src_adouble_path, dst_adouble_path));
+
+	rc = rename(src_adouble_path, dst_adouble_path);
+	if (errno == ENOENT) {
+		rc = 0;
+	}
+
+	TALLOC_FREE(src_adouble_path);
+	TALLOC_FREE(dst_adouble_path);
+
+done:
+	return rc;
+}
+
+static int fruit_unlink(vfs_handle_struct *handle,
+			const struct smb_filename *smb_fname)
+{
+	int rc = -1;
+	struct fruit_config_data *config = NULL;
+	char *adp = NULL;
+
+	if (!is_ntfs_stream_smb_fname(smb_fname)) {
+		return SMB_VFS_NEXT_UNLINK(handle, smb_fname);
+	}
+
+	SMB_VFS_HANDLE_GET_DATA(handle, config,
+				struct fruit_config_data, return -1);
+
+	if (is_afpinfo_stream(smb_fname)) {
+		if (config->meta == FRUIT_META_STREAM) {
+			rc = SMB_VFS_NEXT_UNLINK(handle, smb_fname);
+		} else {
+			rc = SMB_VFS_REMOVEXATTR(handle->conn,
+						 smb_fname->base_name,
+						 AFPINFO_EA_NETATALK);
+		}
+	} else if (is_afpresource_stream(smb_fname)) {
+		if (config->rsrc == FRUIT_RSRC_ADFILE) {
+			rc = adouble_path(talloc_tos(),
+					  smb_fname->base_name, &adp);
+			if (rc != 0) {
+				return -1;
+			}
+			/* FIXME: direct unlink(), missing smb_fname */
+			rc = unlink(adp);
+			if ((rc == -1) && (errno == ENOENT)) {
+				rc = 0;
+			}
+		} else {
+			rc = SMB_VFS_REMOVEXATTR(handle->conn,
+                                     smb_fname->base_name,
+                                     AFPRESOURCE_EA_NETATALK);
+		}
+	} else {
+		rc = SMB_VFS_NEXT_UNLINK(handle, smb_fname);
+	}
+
+	TALLOC_FREE(adp);
+	return rc;
+}
+
+static int fruit_chmod(vfs_handle_struct *handle,
+		       const char *path,
+		       mode_t mode)
+{
+	int rc = -1;
+	char *adp = NULL;
+	struct fruit_config_data *config = NULL;
+	SMB_STRUCT_STAT sb;
+
+	rc = SMB_VFS_NEXT_CHMOD(handle, path, mode);
+	if (rc != 0) {
+		return rc;
+	}
+
+	SMB_VFS_HANDLE_GET_DATA(handle, config,
+				struct fruit_config_data, return -1);
+
+	if (config->rsrc == FRUIT_RSRC_XATTR) {
+		return 0;
+	}
+
+	/* FIXME: direct sys_lstat(), missing smb_fname */
+	rc = sys_lstat(path, &sb, false);
+	if (rc != 0 || !S_ISREG(sb.st_ex_mode)) {
+		return rc;
+	}
+
+	rc = adouble_path(talloc_tos(), path, &adp);
+	if (rc != 0) {
+		return -1;
+	}
+
+	DEBUG(10, ("fruit_chmod: %s\n", adp));
+
+	rc = SMB_VFS_NEXT_CHMOD(handle, adp, mode);
+	if (errno == ENOENT) {
+		rc = 0;
+	}
+
+	TALLOC_FREE(adp);
+	return rc;
+}
+
+static int fruit_chown(vfs_handle_struct *handle,
+		       const char *path,
+		       uid_t uid,
+		       gid_t gid)
+{
+	int rc = -1;
+	char *adp = NULL;
+	struct fruit_config_data *config = NULL;
+	SMB_STRUCT_STAT sb;
+
+	rc = SMB_VFS_NEXT_CHOWN(handle, path, uid, gid);
+	if (rc != 0) {
+		return rc;
+	}
+
+	SMB_VFS_HANDLE_GET_DATA(handle, config,
+				struct fruit_config_data, return -1);
+
+	if (config->rsrc == FRUIT_RSRC_XATTR) {
+		return rc;
+	}
+
+	/* FIXME: direct sys_lstat(), missing smb_fname */
+	rc = sys_lstat(path, &sb, false);
+	if (rc != 0 || !S_ISREG(sb.st_ex_mode)) {
+		return rc;
+	}
+
+	rc = adouble_path(talloc_tos(), path, &adp);
+	if (rc != 0) {
+		goto done;
+	}
+
+	DEBUG(10, ("fruit_chown: %s\n", adp));
+
+	rc = SMB_VFS_NEXT_CHOWN(handle, adp, uid, gid);
+	if (errno == ENOENT) {
+		rc = 0;
+	}
+
+ done:
+	TALLOC_FREE(adp);
+	return rc;
+}
+
+static int fruit_rmdir(struct vfs_handle_struct *handle, const char *path)
+{
+	DIR *dh = NULL;
+	struct dirent *de;
+	struct fruit_config_data *config;
+
+	SMB_VFS_HANDLE_GET_DATA(handle, config,
+				struct fruit_config_data, return -1);
+
+	if (!handle->conn->cwd || !path || (config->rsrc == FRUIT_RSRC_XATTR)) {
+		goto exit_rmdir;
+	}
+
+	/*
+	 * Due to there is no way to change bDeleteVetoFiles variable
+	 * from this module, need to clean up ourselves
+	 */
+	dh = opendir(path);
+	if (dh == NULL) {
+		goto exit_rmdir;
+	}
+
+	while ((de = readdir(dh)) != NULL) {
+		if ((strncmp(de->d_name,
+			     ADOUBLE_NAME_PREFIX,
+			     strlen(ADOUBLE_NAME_PREFIX))) == 0) {
+			char *p = talloc_asprintf(talloc_tos(),
+						  "%s/%s",
+						  path, de->d_name);
+			if (p == NULL) {
+				goto exit_rmdir;
+			}
+			DEBUG(10, ("fruit_rmdir: delete %s\n", p));
+			(void)unlink(p);
+			TALLOC_FREE(p);
+		}
+	}
+
+exit_rmdir:
+	if (dh) {
+		closedir(dh);
+	}
+	return SMB_VFS_NEXT_RMDIR(handle, path);
+}
+
+static ssize_t fruit_pread(vfs_handle_struct *handle,
+			   files_struct *fsp, void *data,
+			   size_t n, off_t offset)
+{
+	int rc = 0;
+        struct adouble *ad = (struct adouble *)VFS_FETCH_FSP_EXTENSION(
+		handle, fsp);
+	struct fruit_config_data *config = NULL;
+	AfpInfo *ai = NULL;
+	ssize_t len;
+	char *name = NULL;
+	char *tmp_base_name = NULL;
+	NTSTATUS status;
+
+	DEBUG(10, ("fruit_pread: offset=%d, size=%d\n", (int)offset, (int)n));
+
+	if (!fsp->base_fsp) {
+		return SMB_VFS_NEXT_PREAD(handle, fsp, data, n, offset);
+	}
+
+	SMB_VFS_HANDLE_GET_DATA(handle, config,
+				struct fruit_config_data, return -1);
+
+	/* fsp_name is not converted with vfs_catia */
+	tmp_base_name = fsp->base_fsp->fsp_name->base_name;
+	status = SMB_VFS_TRANSLATE_NAME(handle->conn,
+					fsp->base_fsp->fsp_name->base_name,
+					vfs_translate_to_unix,
+					talloc_tos(), &name);
+	if (!NT_STATUS_IS_OK(status)) {
+		errno = map_errno_from_nt_status(status);
+		rc = -1;
+		goto exit;
+	}
+	fsp->base_fsp->fsp_name->base_name = name;
+
+	if (ad == NULL) {
+		len = SMB_VFS_NEXT_PREAD(handle, fsp, data, n, offset);
+		if (len == -1) {
+			rc = -1;
+			goto exit;
+		}
+		goto exit;
+	}
+
+	if (!fruit_fsp_recheck(ad, fsp)) {
+		rc = -1;
+		goto exit;
+	}
+
+	if (ad->ad_type == ADOUBLE_META) {
+		ai = afpinfo_new(talloc_tos());
+		if (ai == NULL) {
+			rc = -1;
+			goto exit;
+		}
+
+		len = ad_read(ad, fsp->base_fsp->fsp_name->base_name);
+		if (len == -1) {
+			rc = -1;
+			goto exit;
+		}
+
+		memcpy(&ai->afpi_FinderInfo[0],
+		       ad_entry(ad, ADEID_FINDERI),
+		       ADEDLEN_FINDERI);
+		len = afpinfo_pack(ai, data);
+		if (len != AFP_INFO_SIZE) {
+			rc = -1;
+			goto exit;
+		}
+	} else {
+		len = SMB_VFS_NEXT_PREAD(
+			handle, fsp, data, n,
+			offset + ad_getentryoff(ad, ADEID_RFORK));
+		if (len == -1) {
+			rc = -1;
+			goto exit;
+		}
+	}
+exit:
+	fsp->base_fsp->fsp_name->base_name = tmp_base_name;
+	TALLOC_FREE(name);
+	TALLOC_FREE(ai);
+	if (rc != 0) {
+		len = -1;
+	}
+	DEBUG(10, ("fruit_pread: rc=%d, len=%zd\n", rc, len));
+	return len;
+}
+
+static ssize_t fruit_pwrite(vfs_handle_struct *handle,
+			    files_struct *fsp, const void *data,
+			    size_t n, off_t offset)
+{
+	int rc = 0;
+	struct adouble *ad = (struct adouble *)VFS_FETCH_FSP_EXTENSION(
+		handle, fsp);
+	struct fruit_config_data *config = NULL;
+	AfpInfo *ai = NULL;
+	ssize_t len, new_rfork_size;
+	char *name = NULL;
+	char *tmp_base_name = NULL;
+	NTSTATUS status;
+
+	DEBUG(10, ("fruit_pwrite: offset=%d, size=%d\n", (int)offset, (int)n));
+
+	if (!fsp->base_fsp) {
+		return SMB_VFS_NEXT_PWRITE(handle, fsp, data, n, offset);
+	}
+
+	SMB_VFS_HANDLE_GET_DATA(handle, config,
+				struct fruit_config_data, return -1);
+
+	tmp_base_name = fsp->base_fsp->fsp_name->base_name;
+	status = SMB_VFS_TRANSLATE_NAME(handle->conn,
+					fsp->base_fsp->fsp_name->base_name,
+					vfs_translate_to_unix,
+					talloc_tos(), &name);
+	if (!NT_STATUS_IS_OK(status)) {
+		errno = map_errno_from_nt_status(status);
+		rc = -1;
+		goto exit;
+	}
+	fsp->base_fsp->fsp_name->base_name = name;
+
+	if (ad == NULL) {
+		len = SMB_VFS_NEXT_PWRITE(handle, fsp, data, n, offset);
+		if (len != n) {
+			rc = -1;
+			goto exit;
+		}
+		goto exit;
+	}
+
+	if (!fruit_fsp_recheck(ad, fsp)) {
+		rc = -1;
+		goto exit;
+	}
+
+	if (ad->ad_type == ADOUBLE_META) {
+		if (n != AFP_INFO_SIZE || offset != 0) {
+			DEBUG(1, ("unexpected offset=%jd or size=%jd\n",
+				  (intmax_t)offset, (intmax_t)n));
+			rc = -1;
+			goto exit;
+		}
+		ai = afpinfo_unpack(talloc_tos(), data);
+		if (ai == NULL) {
+			rc = -1;
+			goto exit;
+		}
+		memcpy(ad_entry(ad, ADEID_FINDERI),
+		       &ai->afpi_FinderInfo[0], ADEDLEN_FINDERI);
+		rc = ad_write(ad, name);
+	} else {
+		len = SMB_VFS_NEXT_PWRITE(handle, fsp, data, n,
+                                   offset + ad_getentryoff(ad, ADEID_RFORK));
+		if (len != n) {
+			rc = -1;
+			goto exit;
+		}
+
+		if (config->rsrc == FRUIT_RSRC_ADFILE) {
+			rc = ad_read(ad, name);
+			if (rc == -1) {
+				rc = -1;
+				goto exit;
+			}
+			rc = 0;
+
+			new_rfork_size = len + offset
+				+ ad_getentryoff(ad, ADEID_RFORK);
+			if (new_rfork_size > ad_getentrylen(ad, ADEID_RFORK)) {
+				ad_setentrylen(ad, ADEID_RFORK,
+					       new_rfork_size);
+				rc = ad_write(ad, name);
+			}
+		}
+	}
+
+exit:
+	fsp->base_fsp->fsp_name->base_name = tmp_base_name;
+	TALLOC_FREE(name);
+	TALLOC_FREE(ai);
+	if (rc != 0) {
+		return -1;
+	}
+	return n;
+}
+
+/**
+ * Helper to stat/lstat the base file of an smb_fname.
+ */
+static int fruit_stat_base(vfs_handle_struct *handle,
+			   struct smb_filename *smb_fname,
+			   bool follow_links)
+{
+	char *tmp_stream_name;
+	int rc;
+
+	tmp_stream_name = smb_fname->stream_name;
+	smb_fname->stream_name = NULL;
+	if (follow_links) {
+		rc = SMB_VFS_NEXT_STAT(handle, smb_fname);
+	} else {
+		rc = SMB_VFS_NEXT_LSTAT(handle, smb_fname);
+	}
+	smb_fname->stream_name = tmp_stream_name;
+	return rc;
+}
+
+static int fruit_stat_meta(vfs_handle_struct *handle,
+			   struct smb_filename *smb_fname,
+			   bool follow_links)
+{
+	/* Populate the stat struct with info from the base file. */
+	if (fruit_stat_base(handle, smb_fname, follow_links) == -1) {
+		return -1;
+	}
+	smb_fname->st.st_ex_size = AFP_INFO_SIZE;
+	smb_fname->st.st_ex_ino = fruit_inode(&smb_fname->st,
+					      smb_fname->stream_name);
+	return 0;
+}
+
+static int fruit_stat_rsrc(vfs_handle_struct *handle,
+			   struct smb_filename *smb_fname,
+			   bool follow_links)
+
+{
+	struct adouble *ad = NULL;
+
+	DEBUG(10, ("fruit_stat_rsrc called for %s\n",
+		   smb_fname_str_dbg(smb_fname)));
+
+	ad = ad_get(talloc_tos(), handle, smb_fname->base_name, ADOUBLE_RSRC);
+	if (ad == NULL) {
+		errno = ENOENT;
+		return -1;
+	}
+
+	/* Populate the stat struct with info from the base file. */
+	if (fruit_stat_base(handle, smb_fname, follow_links) == -1) {
+		TALLOC_FREE(ad);
+		return -1;
+	}
+
+	smb_fname->st.st_ex_size = ad_getentrylen(ad, ADEID_RFORK);
+	smb_fname->st.st_ex_ino = fruit_inode(&smb_fname->st,
+					      smb_fname->stream_name);
+	TALLOC_FREE(ad);
+	return 0;
+}
+
+static int fruit_stat(vfs_handle_struct *handle,
+		      struct smb_filename *smb_fname)
+{
+	int rc = -1;
+
+	DEBUG(10, ("fruit_stat called for %s\n",
+		   smb_fname_str_dbg(smb_fname)));
+
+	if (!is_ntfs_stream_smb_fname(smb_fname)
+	    || is_ntfs_default_stream_smb_fname(smb_fname)) {
+		rc = SMB_VFS_NEXT_STAT(handle, smb_fname);
+		if (rc == 0) {
+			update_btime(handle, smb_fname);
+		}
+		return rc;
+	}
+
+	/*
+	 * Note if lp_posix_paths() is true, we can never
+	 * get here as is_ntfs_stream_smb_fname() is
+	 * always false. So we never need worry about
+	 * not following links here.
+	 */
+
+	if (is_afpinfo_stream(smb_fname)) {
+		rc = fruit_stat_meta(handle, smb_fname, true);
+	} else if (is_afpresource_stream(smb_fname)) {
+		rc = fruit_stat_rsrc(handle, smb_fname, true);
+	} else {
+		return SMB_VFS_NEXT_STAT(handle, smb_fname);
+	}
+
+	if (rc == 0) {
+		update_btime(handle, smb_fname);
+		smb_fname->st.st_ex_mode &= ~S_IFMT;
+		smb_fname->st.st_ex_mode |= S_IFREG;
+		smb_fname->st.st_ex_blocks =
+			smb_fname->st.st_ex_size / STAT_ST_BLOCKSIZE + 1;
+	}
+	return rc;
+}
+
+static int fruit_lstat(vfs_handle_struct *handle,
+		       struct smb_filename *smb_fname)
+{
+	int rc = -1;
+
+	DEBUG(10, ("fruit_lstat called for %s\n",
+		   smb_fname_str_dbg(smb_fname)));
+
+	if (!is_ntfs_stream_smb_fname(smb_fname)
+	    || is_ntfs_default_stream_smb_fname(smb_fname)) {
+		rc = SMB_VFS_NEXT_LSTAT(handle, smb_fname);
+		if (rc == 0) {
+			update_btime(handle, smb_fname);
+		}
+		return rc;
+	}
+
+	if (is_afpinfo_stream(smb_fname)) {
+		rc = fruit_stat_meta(handle, smb_fname, false);
+	} else if (is_afpresource_stream(smb_fname)) {
+		rc = fruit_stat_rsrc(handle, smb_fname, false);
+	} else {
+		return SMB_VFS_NEXT_LSTAT(handle, smb_fname);
+	}
+
+	if (rc == 0) {
+		update_btime(handle, smb_fname);
+		smb_fname->st.st_ex_mode &= ~S_IFMT;
+		smb_fname->st.st_ex_mode |= S_IFREG;
+		smb_fname->st.st_ex_blocks =
+			smb_fname->st.st_ex_size / STAT_ST_BLOCKSIZE + 1;
+	}
+	return rc;
+}
+
+static int fruit_fstat_meta(vfs_handle_struct *handle,
+			    files_struct *fsp,
+			    SMB_STRUCT_STAT *sbuf)
+{
+	DEBUG(10, ("fruit_fstat_meta called for %s\n",
+		   smb_fname_str_dbg(fsp->base_fsp->fsp_name)));
+
+	/* Populate the stat struct with info from the base file. */
+	if (fruit_stat_base(handle, fsp->base_fsp->fsp_name, false) == -1) {
+		return -1;
+	}
+	*sbuf = fsp->base_fsp->fsp_name->st;
+	sbuf->st_ex_size = AFP_INFO_SIZE;
+	sbuf->st_ex_ino = fruit_inode(sbuf, fsp->fsp_name->stream_name);
+
+	return 0;
+}
+
+static int fruit_fstat_rsrc(vfs_handle_struct *handle, files_struct *fsp,
+			    SMB_STRUCT_STAT *sbuf)
+{
+	struct fruit_config_data *config;
+	struct adouble *ad = (struct adouble *)VFS_FETCH_FSP_EXTENSION(
+		handle, fsp);
+
+	DEBUG(10, ("fruit_fstat_rsrc called for %s\n",
+		   smb_fname_str_dbg(fsp->base_fsp->fsp_name)));
+
+	SMB_VFS_HANDLE_GET_DATA(handle, config,
+				struct fruit_config_data, return -1);
+
+	if (config->rsrc == FRUIT_RSRC_STREAM) {
+		return SMB_VFS_NEXT_FSTAT(handle, fsp, sbuf);
+	}
+
+	/* Populate the stat struct with info from the base file. */
+	if (fruit_stat_base(handle, fsp->base_fsp->fsp_name, false) == -1) {
+		return -1;
+	}
+	*sbuf = fsp->base_fsp->fsp_name->st;
+	sbuf->st_ex_size = ad_getentrylen(ad, ADEID_RFORK);
+	sbuf->st_ex_ino = fruit_inode(sbuf, fsp->fsp_name->stream_name);
+
+	DEBUG(10, ("fruit_fstat_rsrc %s, size: %zd\n",
+		   smb_fname_str_dbg(fsp->fsp_name),
+		   (ssize_t)sbuf->st_ex_size));
+
+	return 0;
+}
+
+static int fruit_fstat(vfs_handle_struct *handle, files_struct *fsp,
+		       SMB_STRUCT_STAT *sbuf)
+{
+	int rc;
+	char *name = NULL;
+	char *tmp_base_name = NULL;
+	NTSTATUS status;
+	struct adouble *ad = (struct adouble *)
+		VFS_FETCH_FSP_EXTENSION(handle, fsp);
+
+	DEBUG(10, ("fruit_fstat called for %s\n",
+		   smb_fname_str_dbg(fsp->fsp_name)));
+
+	if (fsp->base_fsp) {
+		tmp_base_name = fsp->fsp_name->base_name;
+		/* fsp_name is not converted with vfs_catia */
+		status = SMB_VFS_TRANSLATE_NAME(
+			handle->conn,
+			fsp->base_fsp->fsp_name->base_name,
+			vfs_translate_to_unix,
+			talloc_tos(), &name);
+
+		if (!NT_STATUS_IS_OK(status)) {
+			errno = map_errno_from_nt_status(status);
+			rc = -1;
+			goto exit;
+		}
+		fsp->base_fsp->fsp_name->base_name = name;
+	}
+
+	if (ad == NULL || fsp->base_fsp == NULL) {
+		rc = SMB_VFS_NEXT_FSTAT(handle, fsp, sbuf);
+		goto exit;
+	}
+
+	if (!fruit_fsp_recheck(ad, fsp)) {
+		rc = -1;
+		goto exit;
+	}
+
+	switch (ad->ad_type) {
+	case ADOUBLE_META:
+		rc = fruit_fstat_meta(handle, fsp, sbuf);
+		break;
+	case ADOUBLE_RSRC:
+		rc = fruit_fstat_rsrc(handle, fsp, sbuf);
+		break;
+	default:
+		DEBUG(10, ("fruit_fstat %s: bad type\n",
+			   smb_fname_str_dbg(fsp->fsp_name)));
+		rc = -1;
+		goto exit;
+	}
+
+	if (rc == 0) {
+		sbuf->st_ex_mode &= ~S_IFMT;
+		sbuf->st_ex_mode |= S_IFREG;
+		sbuf->st_ex_blocks = sbuf->st_ex_size / STAT_ST_BLOCKSIZE + 1;
+	}
+
+exit:
+	DEBUG(10, ("fruit_fstat %s, size: %zd\n",
+		   smb_fname_str_dbg(fsp->fsp_name),
+		   (ssize_t)sbuf->st_ex_size));
+	if (tmp_base_name) {
+		fsp->base_fsp->fsp_name->base_name = tmp_base_name;
+	}
+	TALLOC_FREE(name);
+	return rc;
+}
+
+static NTSTATUS fruit_streaminfo(vfs_handle_struct *handle,
+				 struct files_struct *fsp,
+				 const char *fname,
+				 TALLOC_CTX *mem_ctx,
+				 unsigned int *pnum_streams,
+				 struct stream_struct **pstreams)
+{
+	struct fruit_config_data *config = NULL;
+	struct smb_filename *smb_fname = NULL;
+	struct adouble *ad = NULL;
+
+	SMB_VFS_HANDLE_GET_DATA(handle, config, struct fruit_config_data,
+				return NT_STATUS_UNSUCCESSFUL);
+	DEBUG(10, ("fruit_streaminfo called for %s\n", fname));
+
+	smb_fname = synthetic_smb_fname(talloc_tos(), fname, NULL, NULL);
+	if (smb_fname == NULL) {
+		return NT_STATUS_NO_MEMORY;
+	}
+
+	if (config->meta == FRUIT_META_NETATALK) {
+		ad = ad_get(talloc_tos(), handle,
+			    smb_fname->base_name, ADOUBLE_META);
+		if (ad && !empty_finderinfo(ad)) {
+			if (!add_fruit_stream(
+				    mem_ctx, pnum_streams, pstreams,
+				    AFPINFO_STREAM_NAME, AFP_INFO_SIZE,
+				    smb_roundup(handle->conn,
+						AFP_INFO_SIZE))) {
+				TALLOC_FREE(ad);
+				TALLOC_FREE(smb_fname);
+				return NT_STATUS_NO_MEMORY;
+			}
+		}
+		TALLOC_FREE(ad);
+	}
+
+	if (config->rsrc != FRUIT_RSRC_STREAM) {
+		ad = ad_get(talloc_tos(), handle, smb_fname->base_name,
+			    ADOUBLE_RSRC);
+		if (ad) {
+			if (!add_fruit_stream(
+				    mem_ctx, pnum_streams, pstreams,
+				    AFPRESOURCE_STREAM_NAME,
+				    ad_getentrylen(ad, ADEID_RFORK),
+				    smb_roundup(handle->conn,
+						ad_getentrylen(
+							ad, ADEID_RFORK)))) {
+				TALLOC_FREE(ad);
+				TALLOC_FREE(smb_fname);
+				return NT_STATUS_NO_MEMORY;
+			}
+		}
+		TALLOC_FREE(ad);
+	}
+
+	TALLOC_FREE(smb_fname);
+
+	return SMB_VFS_NEXT_STREAMINFO(handle, fsp, fname, mem_ctx,
+				       pnum_streams, pstreams);
+}
+
+static int fruit_ntimes(vfs_handle_struct *handle,
+			const struct smb_filename *smb_fname,
+			struct smb_file_time *ft)
+{
+	int rc = 0;
+	struct adouble *ad = NULL;
+
+	if (null_timespec(ft->create_time)) {
+		goto exit;
+	}
+
+	DEBUG(10,("set btime for %s to %s\n", smb_fname_str_dbg(smb_fname),
+		 time_to_asc(convert_timespec_to_time_t(ft->create_time))));
+
+	ad = ad_get(talloc_tos(), handle, smb_fname->base_name, ADOUBLE_META);
+	if (ad == NULL) {
+		goto exit;
+	}
+
+	ad_setdate(ad, AD_DATE_CREATE | AD_DATE_UNIX,
+		   convert_time_t_to_uint32_t(ft->create_time.tv_sec));
+
+	rc = ad_write(ad, smb_fname->base_name);
+
+exit:
+
+	TALLOC_FREE(ad);
+	if (rc != 0) {
+		DEBUG(1, ("fruit_ntimes: %s\n", smb_fname_str_dbg(smb_fname)));
+		return -1;
+	}
+	return SMB_VFS_NEXT_NTIMES(handle, smb_fname, ft);
+}
+
+static int fruit_fallocate(struct vfs_handle_struct *handle,
+			   struct files_struct *fsp,
+			   enum vfs_fallocate_mode mode,
+			   off_t offset,
+			   off_t len)
+{
+        struct adouble *ad =
+		(struct adouble *)VFS_FETCH_FSP_EXTENSION(handle, fsp);
+
+	if (ad == NULL) {
+		return SMB_VFS_NEXT_FALLOCATE(handle, fsp, mode, offset, len);
+	}
+
+	if (!fruit_fsp_recheck(ad, fsp)) {
+		return errno;
+	}
+
+	/* Let the pwrite code path handle it. */
+	return ENOSYS;
+}
+
+static int fruit_ftruncate(struct vfs_handle_struct *handle,
+			   struct files_struct *fsp,
+			   off_t offset)
+{
+	int rc = 0;
+        struct adouble *ad =
+		(struct adouble *)VFS_FETCH_FSP_EXTENSION(handle, fsp);
+	struct fruit_config_data *config;
+
+	DEBUG(10, ("streams_xattr_ftruncate called for file %s offset %.0f\n",
+		   fsp_str_dbg(fsp), (double)offset));
+
+	if (ad == NULL) {
+		return SMB_VFS_NEXT_FTRUNCATE(handle, fsp, offset);
+	}
+
+	if (!fruit_fsp_recheck(ad, fsp)) {
+		return -1;
+	}
+
+	SMB_VFS_HANDLE_GET_DATA(handle, config,
+				struct fruit_config_data, return -1);
+
+	switch (ad->ad_type) {
+	case ADOUBLE_META:
+		/*
+		 * As this request hasn't been seen in the wild,
+		 * the only sensible use I can imagine is the client
+		 * truncating the stream to 0 bytes size.
+		 * We simply remove the metadata on such a request.
+		 */
+		if (offset == 0) {
+			rc = SMB_VFS_FREMOVEXATTR(fsp,
+						  AFPRESOURCE_EA_NETATALK);
+		}
+		break;
+	case ADOUBLE_RSRC:
+		if (config->rsrc == FRUIT_RSRC_XATTR && offset == 0) {
+			rc = SMB_VFS_FREMOVEXATTR(fsp,
+						  AFPRESOURCE_EA_NETATALK);
+		} else {
+			rc = SMB_VFS_NEXT_FTRUNCATE(
+				handle, fsp,
+				offset + ad_getentryoff(ad, ADEID_RFORK));
+		}
+		break;
+	default:
+		return -1;
+	}
+
+	return rc;
+}
+
+static NTSTATUS fruit_create_file(vfs_handle_struct *handle,
+				  struct smb_request *req,
+				  uint16_t root_dir_fid,
+				  struct smb_filename *smb_fname,
+				  uint32_t access_mask,
+				  uint32_t share_access,
+				  uint32_t create_disposition,
+				  uint32_t create_options,
+				  uint32_t file_attributes,
+				  uint32_t oplock_request,
+				  struct smb2_lease *lease,
+				  uint64_t allocation_size,
+				  uint32_t private_flags,
+				  struct security_descriptor *sd,
+				  struct ea_list *ea_list,
+				  files_struct **result,
+				  int *pinfo)
+{
+	NTSTATUS status;
+	struct fruit_config_data *config = NULL;
+
+	status = SMB_VFS_NEXT_CREATE_FILE(
+		handle, req, root_dir_fid, smb_fname,
+		access_mask, share_access,
+		create_disposition, create_options,
+		file_attributes, oplock_request,
+		lease,
+		allocation_size, private_flags,
+		sd, ea_list, result,
+		pinfo);
+
+	if (!NT_STATUS_IS_OK(status)) {
+		return status;
+	}
+
+	if (is_ntfs_stream_smb_fname(smb_fname)
+	    || (*result == NULL)
+	    || ((*result)->is_directory)) {
+		return status;
+	}
+
+	SMB_VFS_HANDLE_GET_DATA(handle, config, struct fruit_config_data,
+				return NT_STATUS_UNSUCCESSFUL);
+
+	if (config->locking == FRUIT_LOCKING_NETATALK) {
+		status = fruit_check_access(
+			handle, *result,
+			access_mask,
+			map_share_mode_to_deny_mode(share_access, 0));
+		if (!NT_STATUS_IS_OK(status)) {
+			goto fail;
+		}
+	}
+
+	return status;
+
+fail:
+	DEBUG(1, ("fruit_create_file: %s\n", nt_errstr(status)));
+
+	if (*result) {
+		close_file(req, *result, ERROR_CLOSE);
+		*result = NULL;
+	}
+
+	return status;
+}
+
+static struct vfs_fn_pointers vfs_fruit_fns = {
+	.connect_fn = fruit_connect,
+
+	/* File operations */
+	.chmod_fn = fruit_chmod,
+	.chown_fn = fruit_chown,
+	.unlink_fn = fruit_unlink,
+	.rename_fn = fruit_rename,
+	.rmdir_fn = fruit_rmdir,
+	.open_fn = fruit_open,
+	.pread_fn = fruit_pread,
+	.pwrite_fn = fruit_pwrite,
+	.stat_fn = fruit_stat,
+	.lstat_fn = fruit_lstat,
+	.fstat_fn = fruit_fstat,
+	.streaminfo_fn = fruit_streaminfo,
+	.ntimes_fn = fruit_ntimes,
+	.unlink_fn = fruit_unlink,
+	.ftruncate_fn = fruit_ftruncate,
+	.fallocate_fn = fruit_fallocate,
+	.create_file_fn = fruit_create_file,
+};
+
+NTSTATUS vfs_fruit_init(void);
+NTSTATUS vfs_fruit_init(void)
+{
+	NTSTATUS ret = smb_register_vfs(SMB_VFS_INTERFACE_VERSION, "fruit",
+					&vfs_fruit_fns);
+	if (!NT_STATUS_IS_OK(ret)) {
+		return ret;
+	}
+
+	vfs_fruit_debug_level = debug_add_class("fruit");
+	if (vfs_fruit_debug_level == -1) {
+		vfs_fruit_debug_level = DBGC_VFS;
+		DEBUG(0, ("%s: Couldn't register custom debugging class!\n",
+			  "vfs_fruit_init"));
+	} else {
+		DEBUG(10, ("%s: Debug class number of '%s': %d\n",
+			   "vfs_fruit_init","fruit",vfs_fruit_debug_level));
+	}
+
+	return ret;
+}
diff --git a/source3/modules/wscript_build b/source3/modules/wscript_build
index 57daebe..a4a56f7 100644
--- a/source3/modules/wscript_build
+++ b/source3/modules/wscript_build
@@ -82,6 +82,14 @@ bld.SAMBA3_MODULE('vfs_netatalk',
                  internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_netatalk'),
                  enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_netatalk'))
 
+bld.SAMBA3_MODULE('vfs_fruit',
+                 subsystem='vfs',
+                 source='vfs_fruit.c',
+                 deps='samba-util',
+                 init_function='',
+                 internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_fruit'),
+                 enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_fruit'))
+
 bld.SAMBA3_MODULE('vfs_default_quota',
                  subsystem='vfs',
                  source='vfs_default_quota.c',
diff --git a/source3/wscript b/source3/wscript
index 18c3c7d..ecf6cce 100644
--- a/source3/wscript
+++ b/source3/wscript
@@ -1827,7 +1827,7 @@ main() {
                                       auth_script vfs_readahead vfs_xattr_tdb vfs_posix_eadb
                                       vfs_streams_xattr vfs_streams_depot vfs_acl_xattr vfs_acl_tdb
                                       vfs_smb_traffic_analyzer vfs_preopen vfs_catia vfs_scannedonly
-				      vfs_media_harmony
+				      vfs_media_harmony vfs_fruit
 				      vfs_commit
 				      vfs_worm
                                       vfs_crossrename vfs_linux_xfs_sgid
-- 
2.0.0.526.g5318336


From 064d5bf094eaa20811ac2338d1dd3e9b3dc2e788 Mon Sep 17 00:00:00 2001
From: Ralph Boehme <rb at sernet.de>
Date: Mon, 23 Jun 2014 17:01:30 +0200
Subject: [PATCH 03/10] vfs_fruit: add manpage

Signed-off-by: Ralph Boehme <rb at sernet.de>
Reviewed-by: Jeremy Allison <jra at samba.org>
---
 docs-xml/manpages/vfs_fruit.8.xml | 183 ++++++++++++++++++++++++++++++++++++++
 docs-xml/wscript_build            |   1 +
 2 files changed, 184 insertions(+)
 create mode 100644 docs-xml/manpages/vfs_fruit.8.xml

diff --git a/docs-xml/manpages/vfs_fruit.8.xml b/docs-xml/manpages/vfs_fruit.8.xml
new file mode 100644
index 0000000..500fbec
--- /dev/null
+++ b/docs-xml/manpages/vfs_fruit.8.xml
@@ -0,0 +1,183 @@
+<?xml version="1.0" encoding="iso-8859-1"?>
+<!DOCTYPE refentry PUBLIC "-//Samba-Team//DTD DocBook V4.2-Based Variant V1.0//EN" "http://www.samba.org/samba/DTD/samba-doc">
+<refentry id="vfs_fruit.8">
+
+<refmeta>
+	<refentrytitle>vfs_fruit</refentrytitle>
+	<manvolnum>8</manvolnum>
+	<refmiscinfo class="source">Samba</refmiscinfo>
+	<refmiscinfo class="manual">System Administration tools</refmiscinfo>
+	<refmiscinfo class="version">4.1</refmiscinfo>
+</refmeta>
+
+
+<refnamediv>
+	<refname>vfs_fruit</refname>
+	<refpurpose>Enhanced OS X and Netatalk interoperability</refpurpose>
+</refnamediv>
+
+<refsynopsisdiv>
+	<cmdsynopsis>
+		<command>vfs objects = fruit</command>
+	</cmdsynopsis>
+</refsynopsisdiv>
+
+<refsect1>
+	<title>DESCRIPTION</title>
+
+	<para>This VFS module is part of the
+	<citerefentry><refentrytitle>samba</refentrytitle>
+	<manvolnum>7</manvolnum></citerefentry> suite.</para>
+
+	<para>The <command>vfs_fruit</command> module provides
+	enhanced compatibility with Apple SMB clients and
+	interoperability with a Netatalk 3 AFP fileserver.</para>
+
+	<para>The module should be stacked with
+	<command>vfs_catia</command> if enabling character conversion and
+	must be stacked with <command>vfs_streams_xattr</command>, see the
+	example section for the correct config.</para>
+
+	<para>The module enables alternate data streams (ADS) support
+	for a share, intercepts the OS X special streams "AFP_AfpInfo"
+	and "AFP_Resource" and handles them in a special way. All
+	other named streams are deferred to
+	<command>vfs_streams_xattr</command> which must be loaded
+	together with <command>vfs_fruit</command>.</para>
+
+	<para>Having shares with ADS support enabled for OS X client
+	is worthwhile because it resembles the behaviour of Apple's
+	own SMB server implementation and it avoids certain severe
+	performance degradations caused by Samba's case sensitivity
+	semantics.</para>
+
+	<para>The OS X metadata and resource fork stream can be stored
+	in a way compatible with Netatalk 3 by setting
+	<command>fruit:resource = file</command> and
+	<command>fruit:metadata = netatalk</command>.</para>
+
+	<para>OS X maps NTFS illegal characters to the Unicode private
+	range in SMB requests. By setting <command>fruit:encoding =
+	native</command>, all mapped characters are converted to
+	native ASCII characters.</para>
+
+	<para>Finally, share access modes are optionally checked
+	against Netatalk AFP sharing modes by setting
+	<command>fruit:locking = netatalk</command>.</para>
+
+	<para>This module is not stackable other then described in
+	this manpage.</para>
+
+</refsect1>
+
+<refsect1>
+	<title>OPTIONS</title>
+
+	<variablelist>
+
+	  <varlistentry>
+	    <term>fruit:resource = [ file | xattr | stream ]</term>
+	    <listitem>
+	      <para>Controls where the OS X resource fork is stored:</para>
+
+	      <itemizedlist>
+		<listitem><para><command>file (default)</command> - use a ._
+		AppleDouble file compatible with OS X and
+		Netatalk</para></listitem>
+
+		<listitem><para><command>xattr</command> - use a
+		xattr, requires a filesystem with large xattr support
+		and a file IO API compatible with xattrs, this boils
+		down to Solaris and derived platforms and
+		ZFS</para></listitem>
+
+		<listitem><para><command>stream</command> - pass the
+		stream on to the next module in the VFS
+		stack</para></listitem>
+	      </itemizedlist>
+
+	    </listitem>
+	  </varlistentry>
+
+	  <varlistentry>
+	    <term>fruit:metadata = [ stream | netatalk ]</term>
+	    <listitem>
+	      <para>Controls where the OS X metadata stream is stored:</para>
+
+	      <itemizedlist>
+		<listitem><para><command>netatalk (default)</command> - use
+		Netatalk compatible xattr</para></listitem>
+
+		<listitem><para><command>stream</command> - pass the
+		stream on to the next module in the VFS
+		stack</para></listitem>
+	      </itemizedlist>
+
+	    </listitem>
+	  </varlistentry>
+
+	  <varlistentry>
+	    <term>fruit:locking = [ netatalk | none ]</term>
+	    <listitem>
+	      <para></para>
+	      <itemizedlist>
+		<listitem><para><command>none (default)</command> - no
+		cross protocol locking</para></listitem>
+
+		<listitem><para><command>netatalk</command> - use
+		cross protocol locking with Netatalk</para></listitem>
+
+	      </itemizedlist>
+	    </listitem>
+	  </varlistentry>
+
+	  <varlistentry>
+	    <term>fruit:encoding = [ native | private ]</term>
+	    <listitem>
+
+	      <para>Controls how the set of illegal NTFS ASCII
+	      character, commonly used by OS X clients, are stored in
+	      the filesystem:</para>
+
+	      <itemizedlist>
+
+		<listitem><para><command>private (default)</command> -
+		store characters as encoded by the OS X client: mapped
+		to the Unicode private range</para></listitem>
+
+		<listitem><para><command>native</command> - store
+		characters with their native ASCII
+		value</para></listitem>
+
+	      </itemizedlist>
+	    </listitem>
+	  </varlistentry>
+
+	</variablelist>
+</refsect1>
+
+<refsect1>
+	<title>EXAMPLES</title>
+
+<programlisting>
+        <smbconfsection name="[share]"/>
+	<smbconfoption name="vfs objects">catia fruit streams_xattr</smbconfoption>
+	<smbconfoption name="fruit:resource">file</smbconfoption>
+	<smbconfoption name="fruit:metadata">netatalk</smbconfoption>
+	<smbconfoption name="fruit:locking">netatalk</smbconfoption>
+	<smbconfoption name="fruit:encoding">native</smbconfoption>
+</programlisting>
+
+</refsect1>
+
+<refsect1>
+	<title>AUTHOR</title>
+
+	<para>The original Samba software and related utilities
+	were created by Andrew Tridgell. Samba is now developed
+	by the Samba Team as an Open Source project similar
+	to the way the Linux kernel is developed.</para>
+
+</refsect1>
+
+</refentry>
diff --git a/docs-xml/wscript_build b/docs-xml/wscript_build
index 42c9a85..e7c8931 100644
--- a/docs-xml/wscript_build
+++ b/docs-xml/wscript_build
@@ -60,6 +60,7 @@ manpages='''
          manpages/vfs_extd_audit.8
          manpages/vfs_fake_perms.8
          manpages/vfs_fileid.8
+         manpages/vfs_fruit.8
          manpages/vfs_full_audit.8
          manpages/vfs_gpfs.8
          manpages/vfs_linux_xfs_sgid.8
-- 
2.0.0.526.g5318336


From 7cf9e0797f42a0bea62db0b1700548b31e39189b Mon Sep 17 00:00:00 2001
From: Ralph Boehme <rb at sernet.de>
Date: Sat, 28 Jun 2014 01:09:58 +0200
Subject: [PATCH 04/10] s4:torture:smb2: add utility function
 torture_smb2_con_sopt()

Add a utility function that takes an option name as parameter and then
uses the value of the option 'torture:NAME' as share name in a tree
connect.

Signed-off-by: Ralph Boehme <rb at sernet.de>
Reviewed-by: Jeremy Allison <jra at samba.org>
---
 source4/torture/smb2/util.c | 42 ++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 42 insertions(+)

diff --git a/source4/torture/smb2/util.c b/source4/torture/smb2/util.c
index 4297cee..33d2305 100644
--- a/source4/torture/smb2/util.c
+++ b/source4/torture/smb2/util.c
@@ -388,6 +388,48 @@ bool torture_smb2_connection(struct torture_context *tctx, struct smb2_tree **tr
 	return ret;
 }
 
+/**
+ * SMB2 connect with share from soption
+ **/
+bool torture_smb2_con_sopt(struct torture_context *tctx,
+			   const char *soption,
+			   struct smb2_tree **tree)
+{
+	bool ret;
+	struct smbcli_options options;
+	NTSTATUS status;
+	const char *host = torture_setting_string(tctx, "host", NULL);
+	const char *share = torture_setting_string(tctx, soption, NULL);
+	struct cli_credentials *credentials = cmdline_credentials;
+
+	lpcfg_smbcli_options(tctx->lp_ctx, &options);
+
+	if (share == NULL) {
+		printf("No share for option %s\n", soption);
+		return false;
+	}
+
+	status = smb2_connect_ext(tctx,
+				  host,
+				  lpcfg_smb_ports(tctx->lp_ctx),
+				  share,
+				  lpcfg_resolve_context(tctx->lp_ctx),
+				  credentials,
+				  0,
+				  tree,
+				  tctx->ev,
+				  &options,
+				  lpcfg_socket_options(tctx->lp_ctx),
+				  lpcfg_gensec_settings(tctx, tctx->lp_ctx)
+				  );
+	if (!NT_STATUS_IS_OK(status)) {
+		printf("Failed to connect to SMB2 share \\\\%s\\%s - %s\n",
+		       host, share, nt_errstr(status));
+		return false;
+	}
+	return true;
+}
+
 
 /*
   create and return a handle to a test file
-- 
2.0.0.526.g5318336


From 8e5681e84130835fc0977d472630fdbc2e7a86da Mon Sep 17 00:00:00 2001
From: Ralph Boehme <rb at sernet.de>
Date: Tue, 8 Jul 2014 05:36:46 +0200
Subject: [PATCH 05/10] s4:torture: add boilerplate code for testing specific
 VFS modules

Signed-off-by: Ralph Boehme <rb at sernet.de>
Reviewed-by: Jeremy Allison <jra at samba.org>
---
 source4/torture/vfs/vfs.c     | 35 +++++++++++++++++++++++++++++++++++
 source4/torture/wscript_build | 12 +++++++++++-
 2 files changed, 46 insertions(+), 1 deletion(-)
 create mode 100644 source4/torture/vfs/vfs.c

diff --git a/source4/torture/vfs/vfs.c b/source4/torture/vfs/vfs.c
new file mode 100644
index 0000000..7341fba
--- /dev/null
+++ b/source4/torture/vfs/vfs.c
@@ -0,0 +1,35 @@
+/*
+   Unix SMB/CIFS implementation.
+
+   Copyright (C) Ralph Boehme 2014
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+
+#include "torture/smbtorture.h"
+#include "torture/vfs/proto.h"
+
+NTSTATUS torture_vfs_init(void)
+{
+	struct torture_suite *suite = torture_suite_create(
+		talloc_autofree_context(), "vfs");
+
+	suite->description = talloc_strdup(suite, "VFS modules tests");
+
+	torture_register_suite(suite);
+
+	return NT_STATUS_OK;
+}
diff --git a/source4/torture/wscript_build b/source4/torture/wscript_build
index 39192b0..c0de7bb 100755
--- a/source4/torture/wscript_build
+++ b/source4/torture/wscript_build
@@ -150,7 +150,17 @@ bld.SAMBA_MODULE('TORTURE_NTP',
 	internal_module=True
 	)
 
-TORTURE_MODULES = 'TORTURE_BASIC TORTURE_RAW torture_rpc TORTURE_RAP TORTURE_AUTH TORTURE_NBENCH TORTURE_UNIX TORTURE_LDAP TORTURE_NBT TORTURE_NET TORTURE_NTP torture_registry'
+bld.SAMBA_MODULE('TORTURE_VFS',
+	source='vfs/vfs.c',
+	allow_warnings=True,
+	subsystem='smbtorture',
+	deps='LIBCLI_SMB POPT_CREDENTIALS TORTURE_UTIL smbclient-raw TORTURE_RAW',
+	internal_module=True,
+	autoproto='vfs/proto.h',
+	init_function='torture_vfs_init'
+	)
+
+TORTURE_MODULES = 'TORTURE_BASIC TORTURE_RAW torture_rpc TORTURE_RAP TORTURE_AUTH TORTURE_NBENCH TORTURE_UNIX TORTURE_LDAP TORTURE_NBT TORTURE_NET TORTURE_NTP torture_registry TORTURE_VFS'
 
 bld.SAMBA_SUBSYSTEM('torturemain',
                     source='smbtorture.c torture.c shell.c',
-- 
2.0.0.526.g5318336


From 92f640f4bdf02e9248409a86a606264471635b6d Mon Sep 17 00:00:00 2001
From: Ralph Boehme <rb at sernet.de>
Date: Tue, 8 Jul 2014 05:39:49 +0200
Subject: [PATCH 06/10] s4:torture: add wrapper functions

Add wrapper functions that connect two trees with sharenames taken
from passed option.

Signed-off-by: Ralph Boehme <rb at sernet.de>
Reviewed-by: Jeremy Allison <jra at samba.org>
---
 source4/torture/vfs/vfs.c | 77 +++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 77 insertions(+)

diff --git a/source4/torture/vfs/vfs.c b/source4/torture/vfs/vfs.c
index 7341fba..965c7c9 100644
--- a/source4/torture/vfs/vfs.c
+++ b/source4/torture/vfs/vfs.c
@@ -18,9 +18,86 @@
 */
 
 #include "includes.h"
+#include "system/filesys.h"
+#include "libcli/libcli.h"
+#include "../lib/util/dlinklist.h"
 
+#include "libcli/smb2/smb2.h"
+#include "libcli/smb2/smb2_calls.h"
+#include "lib/cmdline/popt_common.h"
+#include "param/param.h"
+#include "libcli/resolve/resolve.h"
+
+#include "torture/util.h"
 #include "torture/smbtorture.h"
 #include "torture/vfs/proto.h"
+#include "torture/smb2/proto.h"
+
+static bool wrap_2ns_smb2_test(struct torture_context *torture_ctx,
+			       struct torture_tcase *tcase,
+			       struct torture_test *test)
+{
+	bool (*fn) (struct torture_context *, struct smb2_tree *, struct smb2_tree *);
+	bool ret = false;
+
+	struct smb2_tree *tree1;
+	struct smb2_tree *tree2;
+	TALLOC_CTX *mem_ctx = talloc_new(torture_ctx);
+
+	if (!torture_smb2_con_sopt(torture_ctx, "share1", &tree1)) {
+		torture_fail(torture_ctx,
+		    "Establishing SMB2 connection failed\n");
+		goto done;
+	}
+
+	talloc_steal(mem_ctx, tree1);
+
+	if (!torture_smb2_con_sopt(torture_ctx, "share2", &tree2)) {
+		torture_fail(torture_ctx,
+		    "Establishing SMB2 connection failed\n");
+		goto done;
+	}
+
+	talloc_steal(mem_ctx, tree2);
+
+	fn = test->fn;
+
+	ret = fn(torture_ctx, tree1, tree2);
+
+done:
+	/* the test may already have closed some of the connections */
+	talloc_free(mem_ctx);
+
+	return ret;
+}
+
+/*
+ * Run a test with 2 connected trees, Share names to connect are taken
+ * from option strings "torture:share1" and "torture:share2"
+ */
+struct torture_test *torture_suite_add_2ns_smb2_test(struct torture_suite *suite,
+						     const char *name,
+						     bool (*run)(struct torture_context *,
+								 struct smb2_tree *,
+								 struct smb2_tree *))
+{
+	struct torture_test *test;
+	struct torture_tcase *tcase;
+
+	tcase = torture_suite_add_tcase(suite, name);
+
+	test = talloc(tcase, struct torture_test);
+
+	test->name = talloc_strdup(test, name);
+	test->description = NULL;
+	test->run = wrap_2ns_smb2_test;
+	test->fn = run;
+	test->dangerous = false;
+
+	DLIST_ADD_END(tcase->tests, test, struct torture_test *);
+
+	return test;
+}
 
 NTSTATUS torture_vfs_init(void)
 {
-- 
2.0.0.526.g5318336


From 4633397570fa654b050633ea912e58e9d24ab07d Mon Sep 17 00:00:00 2001
From: Ralph Boehme <rb at sernet.de>
Date: Tue, 8 Jul 2014 05:47:02 +0200
Subject: [PATCH 07/10] s4:torture: add boilerplate code for vfs_fruit

Signed-off-by: Ralph Boehme <rb at sernet.de>
Reviewed-by: Jeremy Allison <jra at samba.org>
---
 selftest/target/Samba3.pm   |  8 ++++++++
 selftest/target/Samba4.pm   |  8 ++++++++
 source3/selftest/tests.py   |  7 ++++++-
 source4/torture/vfs/fruit.c | 44 ++++++++++++++++++++++++++++++++++++++++++++
 source4/torture/vfs/vfs.c   |  2 ++
 5 files changed, 68 insertions(+), 1 deletion(-)
 create mode 100644 source4/torture/vfs/fruit.c

diff --git a/selftest/target/Samba3.pm b/selftest/target/Samba3.pm
index 394d637..a17f2f6 100755
--- a/selftest/target/Samba3.pm
+++ b/selftest/target/Samba3.pm
@@ -1169,6 +1169,14 @@ sub provision($$$$$$)
 
 [print\$]
 	copy = tmp
+
+[vfs_fruit]
+	path = $shrdir
+	vfs objects = catia fruit streams_xattr
+	fruit:ressource = file
+	fruit:metadata = netatalk
+	fruit:locking = netatalk
+	fruit:encoding = native
 	";
 	close(CONF);
 
diff --git a/selftest/target/Samba4.pm b/selftest/target/Samba4.pm
index 412fbff..ec0f439 100755
--- a/selftest/target/Samba4.pm
+++ b/selftest/target/Samba4.pm
@@ -879,6 +879,14 @@ sub provision($$$$$$$$$)
 	copy = simple
 	ntvfs handler = cifsposix
 
+[vfs_fruit]
+	path = $ctx->{share}
+	vfs objects = catia fruit streams_xattr
+	fruit:ressource = file
+	fruit:metadata = netatalk
+	fruit:locking = netatalk
+	fruit:encoding = native
+
 $extra_smbconf_shares
 ";
 
diff --git a/source3/selftest/tests.py b/source3/selftest/tests.py
index e9b91d4..b4e0386 100755
--- a/source3/selftest/tests.py
+++ b/source3/selftest/tests.py
@@ -289,7 +289,9 @@ nbt = ["nbt.dgram" ]
 
 libsmbclient = ["libsmbclient"]
 
-tests= base + raw + smb2 + rpc + unix + local + rap + nbt + libsmbclient + idmap
+vfs = ["vfs.fruit"]
+
+tests= base + raw + smb2 + rpc + unix + local + rap + nbt + libsmbclient + idmap + vfs
 
 for t in tests:
     if t == "base.delaywrite":
@@ -360,6 +362,9 @@ for t in tests:
 # test the dirsort module.
         plansmbtorture4testsuite(t, "s3dc", '//$SERVER_IP/tmpsort -U$USERNAME%$PASSWORD')
         plansmbtorture4testsuite(t, "plugin_s4_dc", '//$SERVER/tmp -U$USERNAME%$PASSWORD')
+    elif t == "vfs.fruit":
+        plansmbtorture4testsuite(t, "s3dc", '//$SERVER_IP/tmp -U$USERNAME%$PASSWORD --option=torture:share1=vfs_fruit --option=torture:share2=tmp --option=torture:localdir=$SELFTEST_PREFIX/s3dc/share')
+        plansmbtorture4testsuite(t, "plugin_s4_dc", '//$SERVER_IP/tmp -U$USERNAME%$PASSWORD --option=torture:share1=vfs_fruit --option=torture:share2=tmp --option=torture:localdir=$SELFTEST_PREFIX/plugin_s4_dc/share')
     else:
         plansmbtorture4testsuite(t, "s3dc", '//$SERVER_IP/tmp -U$USERNAME%$PASSWORD')
         plansmbtorture4testsuite(t, "plugin_s4_dc", '//$SERVER/tmp -U$USERNAME%$PASSWORD')
diff --git a/source4/torture/vfs/fruit.c b/source4/torture/vfs/fruit.c
new file mode 100644
index 0000000..2685a8f
--- /dev/null
+++ b/source4/torture/vfs/fruit.c
@@ -0,0 +1,44 @@
+/*
+   Unix SMB/CIFS implementation.
+
+   vfs_fruit tests
+
+   Copyright (C) Ralph Boehme 2014
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+
+#include "torture/torture.h"
+#include "torture/util.h"
+#include "torture/smb2/proto.h"
+#include "torture/vfs/proto.h"
+
+/*
+ * Note: This test depends on "vfs objects = catia fruit
+ * streams_xattr".  Note: To run this test, use
+ * "--option=torture:share1=<SHARENAME1>
+ * --option=torture:share2=<SHARENAME2>
+ * --option=torture:localpath=<SHAREPATH>"
+ */
+struct torture_suite *torture_vfs_fruit(void)
+{
+	struct torture_suite *suite = torture_suite_create(
+		talloc_autofree_context(), "fruit");
+
+	suite->description = talloc_strdup(suite, "vfs_fruit tests");
+
+	return suite;
+}
diff --git a/source4/torture/vfs/vfs.c b/source4/torture/vfs/vfs.c
index 965c7c9..9b82387 100644
--- a/source4/torture/vfs/vfs.c
+++ b/source4/torture/vfs/vfs.c
@@ -106,6 +106,8 @@ NTSTATUS torture_vfs_init(void)
 
 	suite->description = talloc_strdup(suite, "VFS modules tests");
 
+	torture_suite_add_suite(suite, torture_vfs_fruit());
+
 	torture_register_suite(suite);
 
 	return NT_STATUS_OK;
-- 
2.0.0.526.g5318336


From 774618a02914131f62372358bcd0ddeaaad3cce4 Mon Sep 17 00:00:00 2001
From: Ralph Boehme <rb at sernet.de>
Date: Tue, 8 Jul 2014 05:50:09 +0200
Subject: [PATCH 08/10] s4:torture:vfs_fruit: add test reading Netatalk
 metadata

Signed-off-by: Ralph Boehme <rb at sernet.de>
Reviewed-by: Jeremy Allison <jra at samba.org>
---
 source4/torture/vfs/fruit.c   | 355 +++++++++++++++++++++++++++++++++++++++++-
 source4/torture/wscript_build |   2 +-
 2 files changed, 355 insertions(+), 2 deletions(-)

diff --git a/source4/torture/vfs/fruit.c b/source4/torture/vfs/fruit.c
index 2685a8f..6b2bd89 100644
--- a/source4/torture/vfs/fruit.c
+++ b/source4/torture/vfs/fruit.c
@@ -20,18 +20,369 @@
 */
 
 #include "includes.h"
+#include "system/filesys.h"
+#include "libcli/libcli.h"
+#include "libcli/smb2/smb2.h"
+#include "libcli/smb2/smb2_calls.h"
+#include "lib/cmdline/popt_common.h"
+#include "param/param.h"
+#include "libcli/resolve/resolve.h"
+#include "MacExtensions.h"
 
 #include "torture/torture.h"
 #include "torture/util.h"
 #include "torture/smb2/proto.h"
 #include "torture/vfs/proto.h"
 
+#define BASEDIR "vfs_fruit_dir"
+
+#define CHECK_STATUS(status, correct) do { \
+	if (!NT_STATUS_EQUAL(status, correct)) { \
+		torture_result(tctx, TORTURE_FAIL, \
+		    "(%s) Incorrect status %s - should be %s\n", \
+		    __location__, nt_errstr(status), nt_errstr(correct)); \
+		ret = false; \
+		goto done; \
+	}} while (0)
+
+/*
+ * REVIEW:
+ * This is hokey, but what else can we do?
+ */
+#if defined(HAVE_ATTROPEN) || defined(FREEBSD)
+#define AFPINFO_EA_NETATALK "org.netatalk.Metadata"
+#define AFPRESOURCE_EA_NETATALK "org.netatalk.ResourceFork"
+#else
+#define AFPINFO_EA_NETATALK "user.org.netatalk.Metadata"
+#define AFPRESOURCE_EA_NETATALK "user.org.netatalk.ResourceFork"
+#endif
+
+/*
+The metadata xattr char buf below contains the following attributes:
+
+-------------------------------------------------------------------------------
+Entry ID   : 00000008 : File Dates Info
+Offset     : 00000162 : 354
+Length     : 00000010 : 16
+
+-DATE------:          : (GMT)                    : (Local)
+create     : 1B442169 : Mon Jun 30 13:23:53 2014 : Mon Jun 30 15:23:53 2014
+modify     : 1B442169 : Mon Jun 30 13:23:53 2014 : Mon Jun 30 15:23:53 2014
+backup     : 80000000 : Unknown or Initial
+access     : 1B442169 : Mon Jun 30 13:23:53 2014 : Mon Jun 30 15:23:53 2014
+
+-RAW DUMP--:  0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F : (ASCII)
+00000000   : 1B 44 21 69 1B 44 21 69 80 00 00 00 1B 44 21 69 : .D!i.D!i.....D!i
+
+-------------------------------------------------------------------------------
+Entry ID   : 00000009 : Finder Info
+Offset     : 0000007A : 122
+Length     : 00000020 : 32
+
+-FInfo-----:
+Type       : 42415252 : BARR
+Creator    : 464F4F4F : FOOO
+isAlias    : 0
+Invisible  : 1
+hasBundle  : 0
+nameLocked : 0
+Stationery : 0
+CustomIcon : 0
+Reserved   : 0
+Inited     : 0
+NoINITS    : 0
+Shared     : 0
+SwitchLaunc: 0
+Hidden Ext : 0
+color      : 000      : none
+isOnDesk   : 0
+Location v : 0000     : 0
+Location h : 0000     : 0
+Fldr       : 0000     : ..
+
+-FXInfo----:
+Rsvd|IconID: 0000     : 0
+Rsvd       : 0000     : ..
+Rsvd       : 0000     : ..
+Rsvd       : 0000     : ..
+AreInvalid : 0
+unknown bit: 0
+unknown bit: 0
+unknown bit: 0
+unknown bit: 0
+unknown bit: 0
+unknown bit: 0
+CustomBadge: 0
+ObjctIsBusy: 0
+unknown bit: 0
+unknown bit: 0
+unknown bit: 0
+unknown bit: 0
+RoutingInfo: 0
+unknown bit: 0
+unknown bit: 0
+Rsvd|commnt: 0000     : 0
+PutAway    : 00000000 : 0
+
+-RAW DUMP--:  0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F : (ASCII)
+00000000   : 42 41 52 52 46 4F 4F 4F 40 00 00 00 00 00 00 00 : BARRFOOO at .......
+00000010   : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 : ................
+
+-------------------------------------------------------------------------------
+Entry ID   : 0000000E : AFP File Info
+Offset     : 00000172 : 370
+Length     : 00000004 : 4
+
+-RAW DUMP--:  0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F : (ASCII)
+00000000   : 00 00 01 A1                                     : ....
+ */
+
+char metadata_xattr[] = {
+	0x00, 0x05, 0x16, 0x07, 0x00, 0x02, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x08, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00,
+	0x00, 0x9a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x08, 0x00, 0x00, 0x01, 0x62, 0x00, 0x00,
+	0x00, 0x10, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00,
+	0x00, 0x7a, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00,
+	0x00, 0x0e, 0x00, 0x00, 0x01, 0x72, 0x00, 0x00,
+	0x00, 0x04, 0x80, 0x44, 0x45, 0x56, 0x00, 0x00,
+	0x01, 0x76, 0x00, 0x00, 0x00, 0x08, 0x80, 0x49,
+	0x4e, 0x4f, 0x00, 0x00, 0x01, 0x7e, 0x00, 0x00,
+	0x00, 0x08, 0x80, 0x53, 0x59, 0x4e, 0x00, 0x00,
+	0x01, 0x86, 0x00, 0x00, 0x00, 0x08, 0x80, 0x53,
+	0x56, 0x7e, 0x00, 0x00, 0x01, 0x8e, 0x00, 0x00,
+	0x00, 0x04, 0x42, 0x41, 0x52, 0x52, 0x46, 0x4f,
+	0x4f, 0x4f, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x1b, 0x44, 0x21, 0x69, 0x1b, 0x44,
+	0x21, 0x69, 0x80, 0x00, 0x00, 0x00, 0x1b, 0x44,
+	0x21, 0x69, 0x00, 0x00, 0x01, 0xa1, 0x00, 0xfd,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc1, 0x20,
+	0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf1, 0xe3,
+	0x86, 0x53, 0x00, 0x00, 0x00, 0x00, 0x3b, 0x01,
+	0x00, 0x00
+};
+
+/**
+ * Read 'count' bytes at 'offset' from stream 'fname:sname' and
+ * compare against buffer 'value'
+ **/
+static bool check_stream(struct smb2_tree *tree,
+			 const char *location,
+			 struct torture_context *tctx,
+			 TALLOC_CTX *mem_ctx,
+			 const char *fname,
+			 const char *sname,
+			 off_t read_offset,
+			 size_t read_count,
+			 off_t comp_offset,
+			 size_t comp_count,
+			 const char *value)
+{
+	struct smb2_handle handle;
+	struct smb2_create create;
+	struct smb2_read r;
+	NTSTATUS status;
+	const char *full_name;
+
+	full_name = talloc_asprintf(mem_ctx, "%s%s", fname, sname);
+	if (full_name == NULL) {
+	    torture_comment(tctx, "talloc_asprintf error\n");
+	    return false;
+	}
+	ZERO_STRUCT(create);
+	create.in.desired_access = SEC_FILE_READ_DATA;
+	create.in.file_attributes = FILE_ATTRIBUTE_NORMAL;
+	create.in.create_disposition = NTCREATEX_DISP_OPEN;
+	create.in.fname = full_name;
+
+	torture_comment(tctx, "Open stream %s\n", full_name);
+
+	status = smb2_create(tree, mem_ctx, &create);
+	if (!NT_STATUS_IS_OK(status)) {
+		if (value == NULL) {
+			return true;
+		} else {
+			torture_comment(tctx, "Unable to open stream %s\n",
+			    full_name);
+			sleep(10000000);
+			return false;
+		}
+	}
+
+	handle = create.out.file.handle;
+	if (value == NULL) {
+		return true;
+	}
+
+
+	ZERO_STRUCT(r);
+	r.in.file.handle = handle;
+	r.in.length      = read_count;
+	r.in.offset      = read_offset;
+
+	status = smb2_read(tree, tree, &r);
+
+	if (!NT_STATUS_IS_OK(status)) {
+		torture_comment(tctx, "(%s) Failed to read %lu bytes from "
+		    "stream '%s'\n", location, (long)strlen(value), full_name);
+		return false;
+	}
+
+	if (memcmp(r.out.data.data + comp_offset, value, comp_count) != 0) {
+		torture_comment(tctx, "(%s) Bad data in stream\n", location);
+		return false;
+	}
+
+	smb2_util_close(tree, handle);
+	return true;
+}
+
+static bool torture_setup_local_xattr(struct torture_context *tctx,
+				      const char *path_option,
+				      const char *name,
+				      const char *metadata,
+				      size_t size)
+{
+	int ret = true;
+	int result;
+	const char *spath;
+	char *path;
+
+	spath = torture_setting_string(tctx, path_option, NULL);
+	if (spath == NULL) {
+		printf("No sharepath for option %s\n", path_option);
+		return false;
+	}
+
+	path = talloc_asprintf(tctx, "%s/%s", spath, name);
+
+	result = setxattr(path, AFPINFO_EA_NETATALK, metadata, size, 0);
+	if (result != 0) {
+		ret = false;
+	}
+
+	TALLOC_FREE(path);
+
+	return ret;
+}
+
+/**
+ * Create a file or directory
+ **/
+static bool torture_setup_file(TALLOC_CTX *mem_ctx, struct smb2_tree *tree,
+			       const char *name, bool dir)
+{
+	struct smb2_create io;
+	NTSTATUS status;
+
+	smb2_util_unlink(tree, name);
+	ZERO_STRUCT(io);
+	io.in.desired_access = SEC_FLAG_MAXIMUM_ALLOWED;
+	io.in.file_attributes   = FILE_ATTRIBUTE_NORMAL;
+	io.in.create_disposition = NTCREATEX_DISP_OVERWRITE_IF;
+	io.in.share_access =
+		NTCREATEX_SHARE_ACCESS_DELETE|
+		NTCREATEX_SHARE_ACCESS_READ|
+		NTCREATEX_SHARE_ACCESS_WRITE;
+	io.in.create_options = 0;
+	io.in.fname = name;
+	if (dir) {
+		io.in.create_options = NTCREATEX_OPTIONS_DIRECTORY;
+		io.in.share_access &= ~NTCREATEX_SHARE_ACCESS_DELETE;
+		io.in.file_attributes   = FILE_ATTRIBUTE_DIRECTORY;
+		io.in.create_disposition = NTCREATEX_DISP_CREATE;
+	}
+
+	status = smb2_create(tree, mem_ctx, &io);
+	if (!NT_STATUS_IS_OK(status)) {
+		return false;
+	}
+
+	status = smb2_util_close(tree, io.out.file.handle);
+	if (!NT_STATUS_IS_OK(status)) {
+		return false;
+	}
+
+	return true;
+}
+
+static bool test_read_atalk_metadata(struct torture_context *tctx,
+				     struct smb2_tree *tree1,
+				     struct smb2_tree *tree2)
+{
+	TALLOC_CTX *mem_ctx = talloc_new(tctx);
+	const char *fname = BASEDIR "\\torture_read_metadata";
+	NTSTATUS status;
+	struct smb2_handle testdirh;
+	bool ret = true;
+
+	torture_comment(tctx, "Checking metadata access\n");
+
+	smb2_util_unlink(tree1, fname);
+
+	status = torture_smb2_testdir(tree1, BASEDIR, &testdirh);
+	CHECK_STATUS(status, NT_STATUS_OK);
+	smb2_util_close(tree1, testdirh);
+
+	ret = torture_setup_file(mem_ctx, tree1, fname, false);
+	if (ret == false) {
+		goto done;
+	}
+
+	ret = torture_setup_local_xattr(tctx, "localdir",
+					BASEDIR "/torture_read_metadata",
+					metadata_xattr, sizeof(metadata_xattr));
+	if (ret == false) {
+		goto done;
+	}
+
+	ret &= check_stream(tree1, __location__, tctx, mem_ctx, fname, AFPINFO_STREAM,
+			    0, 60, 0, 4, "AFP");
+
+	ret &= check_stream(tree1, __location__, tctx, mem_ctx, fname, AFPINFO_STREAM,
+			    0, 60, 16, 8, "BARRFOOO");
+
+done:
+	smb2_deltree(tree1, BASEDIR);
+	talloc_free(mem_ctx);
+	return ret;
+}
+
 /*
  * Note: This test depends on "vfs objects = catia fruit
  * streams_xattr".  Note: To run this test, use
  * "--option=torture:share1=<SHARENAME1>
  * --option=torture:share2=<SHARENAME2>
- * --option=torture:localpath=<SHAREPATH>"
+ * --option=torture:localdir=<SHAREPATH>"
  */
 struct torture_suite *torture_vfs_fruit(void)
 {
@@ -40,5 +391,7 @@ struct torture_suite *torture_vfs_fruit(void)
 
 	suite->description = talloc_strdup(suite, "vfs_fruit tests");
 
+	torture_suite_add_2ns_smb2_test(suite, "read metadata", test_read_atalk_metadata);
+
 	return suite;
 }
diff --git a/source4/torture/wscript_build b/source4/torture/wscript_build
index c0de7bb..c13e7d4 100755
--- a/source4/torture/wscript_build
+++ b/source4/torture/wscript_build
@@ -151,7 +151,7 @@ bld.SAMBA_MODULE('TORTURE_NTP',
 	)
 
 bld.SAMBA_MODULE('TORTURE_VFS',
-	source='vfs/vfs.c',
+	source='vfs/vfs.c vfs/fruit.c',
 	allow_warnings=True,
 	subsystem='smbtorture',
 	deps='LIBCLI_SMB POPT_CREDENTIALS TORTURE_UTIL smbclient-raw TORTURE_RAW',
-- 
2.0.0.526.g5318336


From 1cb258cf9c62db5825c95b27c8d617650ac93857 Mon Sep 17 00:00:00 2001
From: Ralph Boehme <rb at sernet.de>
Date: Thu, 10 Jul 2014 16:40:28 +0200
Subject: [PATCH 09/10] s4:torture:vfs_fruit: add test writing Netatalk
 metadata

Signed-off-by: Ralph Boehme <rb at sernet.de>
Reviewed-by: Jeremy Allison <jra at samba.org>
---
 source4/torture/vfs/fruit.c | 138 ++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 138 insertions(+)

diff --git a/source4/torture/vfs/fruit.c b/source4/torture/vfs/fruit.c
index 6b2bd89..453ddf9 100644
--- a/source4/torture/vfs/fruit.c
+++ b/source4/torture/vfs/fruit.c
@@ -192,6 +192,104 @@ char metadata_xattr[] = {
 };
 
 /**
+ * talloc and intialize an AfpInfo
+ **/
+static AfpInfo *torture_afpinfo_new(TALLOC_CTX *mem_ctx)
+{
+	AfpInfo *info;
+
+	info = talloc_zero(mem_ctx, AfpInfo);
+	if (info == NULL) {
+		return NULL;
+	}
+
+	info->afpi_Signature = AFP_Signature;
+	info->afpi_Version = AFP_Version;
+	info->afpi_BackupTime = AFP_BackupTime;
+
+	return info;
+}
+
+/**
+ * Pack AfpInfo into a talloced buffer
+ **/
+static char *torture_afpinfo_pack(TALLOC_CTX *mem_ctx,
+				  AfpInfo *info)
+{
+	char *buf;
+
+	buf = talloc_array(mem_ctx, char, AFP_INFO_SIZE);
+	if (buf == NULL) {
+		return NULL;
+	}
+
+	RSIVAL(buf, 0, info->afpi_Signature);
+	RSIVAL(buf, 4, info->afpi_Version);
+	RSIVAL(buf, 12, info->afpi_BackupTime);
+	memcpy(buf + 16, info->afpi_FinderInfo, sizeof(info->afpi_FinderInfo));
+
+	return buf;
+}
+
+/**
+ * Unpack AfpInfo
+ **/
+#if 0
+static void torture_afpinfo_unpack(AfpInfo *info, char *data)
+{
+	info->afpi_Signature = RIVAL(data, 0);
+	info->afpi_Version = RIVAL(data, 4);
+	info->afpi_BackupTime = RIVAL(data, 12);
+	memcpy(info->afpi_FinderInfo, (const char *)data + 16,
+	       sizeof(info->afpi_FinderInfo));
+}
+#endif
+
+static bool torture_write_afpinfo(struct smb2_tree *tree,
+				  struct torture_context *tctx,
+				  TALLOC_CTX *mem_ctx,
+				  const char *fname,
+				  AfpInfo *info)
+{
+	struct smb2_handle handle;
+	struct smb2_create io;
+	NTSTATUS status;
+	const char *full_name;
+	char *infobuf;
+	bool ret = true;
+
+	full_name = talloc_asprintf(mem_ctx, "%s%s", fname, AFPINFO_STREAM);
+	if (full_name == NULL) {
+	    torture_comment(tctx, "talloc_asprintf error\n");
+	    return false;
+	}
+	ZERO_STRUCT(io);
+	io.in.desired_access = SEC_FILE_WRITE_DATA;
+	io.in.file_attributes = FILE_ATTRIBUTE_NORMAL;
+	io.in.create_disposition = NTCREATEX_DISP_OVERWRITE_IF;
+	io.in.create_options = 0;
+	io.in.fname = full_name;
+
+	status = smb2_create(tree, mem_ctx, &io);
+	CHECK_STATUS(status, NT_STATUS_OK);
+
+	handle = io.out.file.handle;
+
+	infobuf = torture_afpinfo_pack(mem_ctx, info);
+	if (infobuf == NULL) {
+		return false;
+	}
+
+	status = smb2_util_write(tree, handle, infobuf, 0, AFP_INFO_SIZE);
+	CHECK_STATUS(status, NT_STATUS_OK);
+
+	smb2_util_close(tree, handle);
+
+done:
+	return ret;
+}
+
+/**
  * Read 'count' bytes at 'offset' from stream 'fname:sname' and
  * compare against buffer 'value'
  **/
@@ -377,6 +475,45 @@ done:
 	return ret;
 }
 
+static bool test_write_atalk_metadata(struct torture_context *tctx,
+				      struct smb2_tree *tree1,
+				      struct smb2_tree *tree2)
+{
+	TALLOC_CTX *mem_ctx = talloc_new(tctx);
+	const char *fname = BASEDIR "\\torture_write_metadata";
+	const char *type_creator = "SMB,OLE!";
+	NTSTATUS status;
+	struct smb2_handle testdirh;
+	bool ret = true;
+	AfpInfo *info;
+
+	smb2_util_unlink(tree1, fname);
+
+	status = torture_smb2_testdir(tree1, BASEDIR, &testdirh);
+	CHECK_STATUS(status, NT_STATUS_OK);
+	smb2_util_close(tree1, testdirh);
+
+	ret = torture_setup_file(mem_ctx, tree1, fname, false);
+	if (ret == false) {
+		goto done;
+	}
+
+	info = torture_afpinfo_new(mem_ctx);
+	if (info == NULL) {
+		goto done;
+	}
+
+	memcpy(info->afpi_FinderInfo, type_creator, 8);
+	ret = torture_write_afpinfo(tree1, tctx, mem_ctx, fname, info);
+	ret &= check_stream(tree1, __location__, tctx, mem_ctx, fname, AFPINFO_STREAM,
+			    0, 60, 16, 8, type_creator);
+
+done:
+	smb2_deltree(tree1, BASEDIR);
+	talloc_free(mem_ctx);
+	return ret;
+}
+
 /*
  * Note: This test depends on "vfs objects = catia fruit
  * streams_xattr".  Note: To run this test, use
@@ -392,6 +529,7 @@ struct torture_suite *torture_vfs_fruit(void)
 	suite->description = talloc_strdup(suite, "vfs_fruit tests");
 
 	torture_suite_add_2ns_smb2_test(suite, "read metadata", test_read_atalk_metadata);
+	torture_suite_add_2ns_smb2_test(suite, "write metadata", test_write_atalk_metadata);
 
 	return suite;
 }
-- 
2.0.0.526.g5318336


From 684dc0721b9dc72f8f07c94ed91b22b910cb7d75 Mon Sep 17 00:00:00 2001
From: Ralph Boehme <rb at sernet.de>
Date: Fri, 11 Jul 2014 12:58:37 +0200
Subject: [PATCH 10/10] s4:torture:vfs_fruit: add tests for resource fork IO

Signed-off-by: Ralph Boehme <rb at sernet.de>
Reviewed-by: Jeremy Allison <jra at samba.org>
---
 source4/torture/vfs/fruit.c | 110 ++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 110 insertions(+)

diff --git a/source4/torture/vfs/fruit.c b/source4/torture/vfs/fruit.c
index 453ddf9..ad05b9f 100644
--- a/source4/torture/vfs/fruit.c
+++ b/source4/torture/vfs/fruit.c
@@ -364,6 +364,65 @@ static bool check_stream(struct smb2_tree *tree,
 	return true;
 }
 
+/**
+ * Read 'count' bytes at 'offset' from stream 'fname:sname' and
+ * compare against buffer 'value'
+ **/
+static bool write_stream(struct smb2_tree *tree,
+			 const char *location,
+			 struct torture_context *tctx,
+			 TALLOC_CTX *mem_ctx,
+			 const char *fname,
+			 const char *sname,
+			 off_t offset,
+			 size_t size,
+			 const char *value)
+{
+	struct smb2_handle handle;
+	struct smb2_create create;
+	NTSTATUS status;
+	const char *full_name;
+
+	full_name = talloc_asprintf(mem_ctx, "%s%s", fname, sname);
+	if (full_name == NULL) {
+	    torture_comment(tctx, "talloc_asprintf error\n");
+	    return false;
+	}
+	ZERO_STRUCT(create);
+	create.in.desired_access = SEC_FILE_WRITE_DATA;
+	create.in.file_attributes = FILE_ATTRIBUTE_NORMAL;
+	create.in.create_disposition = NTCREATEX_DISP_OPEN_IF;
+	create.in.fname = full_name;
+
+	status = smb2_create(tree, mem_ctx, &create);
+	if (!NT_STATUS_IS_OK(status)) {
+		if (value == NULL) {
+			return true;
+		} else {
+			torture_comment(tctx, "Unable to open stream %s\n",
+			    full_name);
+			sleep(10000000);
+			return false;
+		}
+	}
+
+	handle = create.out.file.handle;
+	if (value == NULL) {
+		return true;
+	}
+
+	status = smb2_util_write(tree, handle, value, offset, size);
+
+	if (!NT_STATUS_IS_OK(status)) {
+		torture_comment(tctx, "(%s) Failed to write %lu bytes to "
+		    "stream '%s'\n", location, (long)size, full_name);
+		return false;
+	}
+
+	smb2_util_close(tree, handle);
+	return true;
+}
+
 static bool torture_setup_local_xattr(struct torture_context *tctx,
 				      const char *path_option,
 				      const char *name,
@@ -514,6 +573,56 @@ done:
 	return ret;
 }
 
+static bool test_write_atalk_rfork_io(struct torture_context *tctx,
+				      struct smb2_tree *tree1,
+				      struct smb2_tree *tree2)
+{
+	TALLOC_CTX *mem_ctx = talloc_new(tctx);
+	const char *fname = BASEDIR "\\torture_write_rfork_io";
+	const char *rfork_content = "1234567890";
+	NTSTATUS status;
+	struct smb2_handle testdirh;
+	bool ret = true;
+
+	smb2_util_unlink(tree1, fname);
+
+	status = torture_smb2_testdir(tree1, BASEDIR, &testdirh);
+	CHECK_STATUS(status, NT_STATUS_OK);
+	smb2_util_close(tree1, testdirh);
+
+	ret = torture_setup_file(mem_ctx, tree1, fname, false);
+	if (ret == false) {
+		goto done;
+	}
+
+	torture_comment(tctx, "(%s) writing to resource fork\n",
+	    __location__);
+
+	ret &= write_stream(tree1, __location__, tctx, mem_ctx,
+			    fname, AFPRESOURCE_STREAM,
+			    10, 10, rfork_content);
+
+	ret &= check_stream(tree1, __location__, tctx, mem_ctx,
+			    fname, AFPRESOURCE_STREAM,
+			    0, 20, 10, 10, rfork_content);
+
+	torture_comment(tctx, "(%s) writing to resource fork at large offset\n",
+	    __location__);
+
+	ret &= write_stream(tree1, __location__, tctx, mem_ctx,
+			    fname, AFPRESOURCE_STREAM,
+			    (off_t)1<<32, 10, rfork_content);
+
+	ret &= check_stream(tree1, __location__, tctx, mem_ctx,
+			    fname, AFPRESOURCE_STREAM,
+			    (off_t)1<<32, 10, 0, 10, rfork_content);
+
+done:
+	smb2_deltree(tree1, BASEDIR);
+	talloc_free(mem_ctx);
+	return ret;
+}
+
 /*
  * Note: This test depends on "vfs objects = catia fruit
  * streams_xattr".  Note: To run this test, use
@@ -530,6 +639,7 @@ struct torture_suite *torture_vfs_fruit(void)
 
 	torture_suite_add_2ns_smb2_test(suite, "read metadata", test_read_atalk_metadata);
 	torture_suite_add_2ns_smb2_test(suite, "write metadata", test_write_atalk_metadata);
+	torture_suite_add_2ns_smb2_test(suite, "resource fork IO", test_write_atalk_rfork_io);
 
 	return suite;
 }
-- 
2.0.0.526.g5318336



More information about the samba-technical mailing list