[Patch] New VFS module vfs_fruit

Ralph Böhme rb at sernet.de
Thu Jul 3 03:41:38 MDT 2014


Hi all,

I know that only 10 days have passed since I sent in the patches and
it's more then 2000 lines of code. But I've got that cold 4.2 freeze
feeeling in the neck and I have evidence that real users might benefit
from this module [1], so I'm giving this a push. :)

Review(s) appreciated.

Thanks!
-Ralph

[1] <https://bugzilla.samba.org/show_bug.cgi?id=10683>

On Mon, Jun 23, 2014 at 05:14:34PM +0200, Ralph Böhme wrote:
> Hi!
> 
> Attached are two patches adding a new VFS module "vfs_fruit" demoed at
> SambaXP as "vfs_apple".
> 
> Several parties expressed concerns wrt to the original module name
> "vfs_apple", so we came up with a different working title "vfs_fruit".
> 
> The module provides enhanced compatibility with Apple SMB clients and
> interoperability with a Netatalk 3 AFP fileserver.
> 
> I'm still working on smbtorture tests for the module, but I'd
> appreciate review and feedback now -- before the 4.2 freeze -- so
> hopefully the patch can be included for 4.2. :)
> 
> Thanks!
> -Ralph
> 
> -- 
> SerNet GmbH, Bahnhofsallee 1b, 37081 Göttingen
> phone: +49-551-370000-0, fax: +49-551-370000-9
> AG Göttingen, HRB 2816, GF: Dr. Johannes Loxen
> http://www.sernet.de,mailto:kontakt@sernet.de

> >From 3ed31c27ee3f3bec136678d70ad645af68c6e8fb 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 1/2] 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>
> ---
>  source3/modules/vfs_fruit.c   | 2815 +++++++++++++++++++++++++++++++++++++++++
>  source3/modules/wscript_build |    8 +
>  source3/wscript               |    2 +-
>  3 files changed, 2824 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..653b61d
> --- /dev/null
> +++ b/source3/modules/vfs_fruit.c
> @@ -0,0 +1,2815 @@
> +/*
> + * 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 ADEDLEN_NENTRIES    2
> +#define AD_HEADER_LEN       (ADEDLEN_MAGIC + ADEDLEN_VERSION + \
> +			     ADEDLEN_FILLER + ADEDLEN_NENTRIES) /* 26 */
> +#define AD_ENTRY_LEN        12  /* size of a single entry header */
> +
> +/* 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         htonl(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 {
> +	off_t     ade_off;
> +	ssize_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)
> +{
> +	int 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)
> +{
> +	int 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;
> +	if (eid == AD_DEV)
> +		return ADEID_PRIVDEV;
> +	if (eid == AD_INO)
> +		return ADEID_PRIVINO;
> +	if (eid == AD_SYN)
> +		return ADEID_PRIVSYN;
> +	if (eid == AD_ID)
> +		return ADEID_PRIVID;
> +
> +	return 0;
> +}
> +
> +/**
> + * Pack AppleDouble structure into data buffer
> + **/
> +static int ad_pack(struct adouble *ad)
> +{
> +	uint32_t       eid;
> +	uint32_t       temp;
> +	uint16_t       nent;
> +	char          *buf, *nentp;
> +	ssize_t        bufsize;
> +
> +	buf = ad->ad_data;
> +	bufsize = talloc_get_size(ad->ad_data);
> +
> +	temp = htonl(ad->ad_magic);
> +	memcpy(buf, &temp, sizeof(temp));
> +	buf += sizeof(temp);
> +	bufsize -= sizeof(temp);
> +	if (bufsize < 0)
> +		return -1;
> +
> +	temp = htonl(ad->ad_version);
> +	memcpy(buf, &temp, sizeof(temp));
> +	buf += sizeof(temp);
> +	bufsize -= sizeof(temp);
> +	if (bufsize < 0)
> +		return -1;
> +
> +	if (ad->ad_type == ADOUBLE_RSRC)
> +		memcpy(buf, "Netatalk        ", ADEDLEN_FILLER);
> +	buf += ADEDLEN_FILLER;
> +	bufsize -= ADEDLEN_FILLER;
> +	if (bufsize < 0)
> +		return -1;
> +
> +	nentp = buf;
> +	buf += sizeof(nent);
> +	bufsize -= sizeof(nent);
> +	if (bufsize < 0)
> +		return -1;
> +
> +	for (eid = 0, nent = 0; eid < ADEID_MAX; eid++) {
> +		if ((ad->ad_eid[ eid ].ade_off == 0))
> +			continue;
> +		temp = htonl(AD_EID_DISK(eid));
> +		memcpy(buf, &temp, sizeof(temp));
> +		buf += sizeof(temp);
> +		bufsize -= sizeof(temp);
> +		if (bufsize < 0)
> +			return -1;
> +
> +		temp = htonl(ad->ad_eid[eid].ade_off);
> +		memcpy(buf, &temp, sizeof(temp));
> +		buf += sizeof(temp);
> +		bufsize -= sizeof(temp);
> +		if (bufsize < 0)
> +			return -1;
> +
> +		temp = htonl(ad->ad_eid[eid].ade_len);
> +		memcpy(buf, &temp, sizeof(temp));
> +		buf += sizeof(temp);
> +		bufsize -= sizeof(temp);
> +		if (bufsize < 0)
> +			return -1;
> +
> +		nent++;
> +	}
> +	nent = htons(nent);
> +	memcpy(nentp, &nent, sizeof(nent));
> +
> +	return 0;
> +}
> +
> +/**
> + * Unpack an AppleDouble blob into a talloc buffer
> + **/
> +static int ad_unpack(struct adouble *ad, char *buf, const int nentries)
> +{
> +	int rc = 0;
> +	uint32_t   eid, adentries, len, off;
> +
> +	memcpy(&ad->ad_magic, ad->ad_data, sizeof(ad->ad_magic));
> +	memcpy(&ad->ad_version,
> +	       ad->ad_data + ADEDOFF_VERSION,
> +	       sizeof(ad->ad_version));
> +	ad->ad_magic = ntohl(ad->ad_magic);
> +	ad->ad_version = ntohl(ad->ad_version);
> +
> +	if ((ad->ad_magic != AD_MAGIC) || (ad->ad_version != AD_VERSION)) {
> +		DEBUG(2, ("wrong magic or version\n"));
> +		errno = EINVAL;
> +		rc = -1;
> +		goto exit;
> +	}
> +
> +	memcpy(&adentries, ad->ad_data + ADEDOFF_NENTRIES, sizeof(adentries));
> +	adentries = ntohs(adentries);
> +	if (adentries != nentries) {
> +		DEBUG(2, ("invalid number of entries: %d\n", adentries));
> +		errno = EINVAL;
> +		rc = -1;
> +		goto exit;
> +	}
> +
> +	buf += AD_HEADER_LEN;
> +
> +	/* now, read in the entry bits */
> +	for (; adentries > 0; adentries--) {
> +		memcpy(&eid, buf, sizeof(eid));
> +		eid = get_eid(ntohl(eid));
> +		buf += sizeof(eid);
> +		memcpy(&off, buf, sizeof(off));
> +		off = ntohl(off);
> +		buf += sizeof(off);
> +		memcpy(&len, buf, sizeof(len));
> +		len = ntohl(len);
> +		buf += sizeof(len);
> +
> +		ad->ad_eid[eid].ade_off = off;
> +		ad->ad_eid[eid].ade_len = len;
> +
> +		if (!eid || eid > ADEID_MAX) {
> +			rc = -1;
> +			DEBUG(1, ("bogus eid %d, \n", eid));
> +		}
> +	}
> +exit:
> +	return rc;
> +}
> +
> +/**
> + * 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;
> +	ssize_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;
> +
> +	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 */
> +	rc = ad_unpack(ad, ad->ad_data, ADEID_NUM_XATTR);
> +	if (rc != 0) {
> +		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;
> +	int fd = -1;
> +	int rc;
> +	ssize_t len;
> +	char *adpath;
> +	bool opened = false;
> +	int mode;
> +	struct adouble *meta_ad;
> +
> +	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;
> +			return -1;
> +#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) {
> +		SMB_STRUCT_STAT sbuf;
> +		/* 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 */
> +		rc = ad_unpack(ad, ad->ad_data, ADEID_NUM_DOT_UND);
> +		if (rc != 0) {
> +			DEBUG(2, ("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)
> +		len = -1;
> +	if (opened && fd != -1)
> +		close(fd);
> +	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, 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 path
> + **/
> +static int adouble_path(TALLOC_CTX *ctx, const char *path_in, char **path_out)
> +{
> +	char *path;
> +	char *sep;
> +
> +	path = talloc_strdup(ctx, path_in);
> +	if (path == NULL)
> +		return -1;
> +
> +	if ((sep = strrchr(path, '/')) != NULL) {
> +		*sep++ = 0;
> +		*path_out = talloc_asprintf(ctx, "%s/._%s", path, sep);
> +		if (*path_out == NULL)
> +			return -1;
> +	 } else {
> +		*path_out = talloc_asprintf(ctx, "._%s", path_in);
> +		if (*path_out == NULL)
> +			return -1;
> +	}
> +
> +	return 0;
> +}
> +
> +/**
> + * Allocate and initialize an AfpInfo struct
> + *
> + * All fields are initialised in network byte order
> + **/
> +static AfpInfo *afpinfo_new(TALLOC_CTX *ctx)
> +{
> +	AfpInfo *ai = talloc_zero(ctx, AfpInfo);
> +	if (ai == NULL)
> +		return NULL;
> +	ai->afpi_Signature = htonl(AFP_Signature);
> +	ai->afpi_Version = htonl(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);
> +	memcpy(buf, &ai->afpi_Signature, sizeof(ai->afpi_Signature));
> +	memcpy(buf + 4, &ai->afpi_Version, sizeof(ai->afpi_Version));
> +	memcpy(buf + 12, &ai->afpi_BackupTime, sizeof(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)
> +{
> +	const char *buf = data;
> +	AfpInfo *ai = talloc_zero(ctx, AfpInfo);
> +	if (ai == NULL)
> +		return NULL;
> +
> +	memcpy(&ai->afpi_Signature, buf, sizeof(ai->afpi_Signature));
> +	memcpy(&ai->afpi_Version, buf + 4, sizeof(ai->afpi_Version));
> +	memcpy(&ai->afpi_BackupTime, buf + 12, sizeof(ai->afpi_BackupTime));
> +	memcpy(ai->afpi_FinderInfo, buf + 16, sizeof(ai->afpi_FinderInfo));
> +
> +	if (ai->afpi_Signature != htonl(AFP_Signature)
> +	    || ai->afpi_Version != htonl(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)
> +		return;
> +
> +	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, 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, 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, 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, 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, *newlist;
> +	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 "*/");
> +	}
> +
> +	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;
> +	struct smb_filename *smb_fname_base = NULL;
> +	int baseflags;
> +	int hostfd = -1;
> +	struct adouble *ad;
> +
> +	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:
> +	if (rc != 0) {
> +		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;
> +	}
> +	DEBUG(10, ("fruit_open meta rc=%d, fd=%d\n", rc, hostfd));
> +	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;
> +	struct adouble *ad;
> +	struct smb_filename *smb_fname_base = NULL;
> +	char *adpath;
> +	int hostfd;
> +
> +	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:
> +	if (rc != 0) {
> +		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;
> +	}
> +	DEBUG(10, ("fruit_open resource fork: rc=%d, fd=%d\n", rc, hostfd));
> +	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 = 0;
> +	char *src_adouble_path;
> +	char *dst_adouble_path;
> +	struct fruit_config_data *config;
> +
> +	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;
> +
> +done:
> +	return rc;
> +}
> +
> +static int fruit_unlink(vfs_handle_struct *handle,
> +			const struct smb_filename *smb_fname)
> +{
> +	int rc;
> +	struct fruit_config_data *config;
> +	char *adp;
> +
> +	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);
> +	}
> +
> +	return rc;
> +}
> +
> +static int fruit_chmod(vfs_handle_struct *handle,
> +		       const char *path,
> +		       mode_t mode)
> +{
> +	int rc;
> +	char *adp;
> +	struct fruit_config_data *config;
> +	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;
> +
> +	return rc;
> +}
> +
> +static int fruit_chown(vfs_handle_struct *handle,
> +		       const char *path,
> +		       uid_t uid,
> +		       gid_t gid)
> +{
> +	int rc;
> +	char *adp;
> +	struct fruit_config_data *config;
> +	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:
> +	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);
> +		}
> +	}
> +
> +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;
> +	AfpInfo *ai = NULL;
> +	ssize_t len;
> +	char *name = NULL;
> +	char *tmp_base_name;
> +	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);
> +	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;
> +	AfpInfo *ai = NULL;
> +	ssize_t len, new_rfork_size;
> +	char *name = NULL;
> +	char *tmp_base_name;
> +	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);
> +	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;
> +
> +	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) {
> +		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);
> +	return 0;
> +}
> +
> +static int fruit_stat(vfs_handle_struct *handle,
> +		      struct smb_filename *smb_fname)
> +{
> +	int rc;
> +
> +	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;
> +
> +	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;
> +	struct smb_filename *smb_fname = NULL;
> +	struct adouble *ad;
> +
> +	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))) {
> +				return NT_STATUS_NO_MEMORY;
> +			}
> +		}
> +	}
> +
> +	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)))) {
> +				return NT_STATUS_NO_MEMORY;
> +			}
> +		}
> +	}
> +
> +	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;
> +
> +	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:
> +	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,
> +				  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;
> +
> +	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,
> +		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 2d5cc6d..4118a6a 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 6e76c86..5caf29b 100644
> --- a/source3/wscript
> +++ b/source3/wscript
> @@ -1824,7 +1824,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
> -- 
> 1.9.3
> 

> >From 33568246d175d3e91b151cc72539edaff2308a63 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 2/2] vfs_fruit: add manpage
> 
> Signed-off-by: Ralph Boehme <rb at sernet.de>
> ---
>  docs-xml/manpages/vfs_fruit.8.xml | 180 ++++++++++++++++++++++++++++++++++++++
>  docs-xml/wscript_build            |   1 +
>  2 files changed, 181 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..b18f39b
> --- /dev/null
> +++ b/docs-xml/manpages/vfs_fruit.8.xml
> @@ -0,0 +1,180 @@
> +<?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 sould 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 degredations 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 charcters 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>
> +
> +</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 d59d4b5..2602244 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
> -- 
> 1.9.3
> 


-- 
-- 
SerNet GmbH, Bahnhofsallee 1b, 37081 Göttingen
phone: +49-551-370000-0, fax: +49-551-370000-9
AG Göttingen, HRB 2816, GF: Dr. Johannes Loxen
http://www.sernet.de,mailto:kontakt@sernet.de


More information about the samba-technical mailing list