[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