VFS: Modify rmdir to take a const struct smb_filename * instead of const char *
[kamenim/samba-autobuild/.git] / source3 / modules / vfs_fruit.c
index 0441d5ebd48501fb969fa76f14ed0e512d1d6709..3cd8c65942adb06f0f5cc1a427f696e28248cfdb 100644 (file)
@@ -28,6 +28,9 @@
 #include "smbd/globals.h"
 #include "messages.h"
 #include "libcli/security/security.h"
+#include "../libcli/smb/smb2_create_ctx.h"
+#include "lib/util/sys_rw.h"
+#include "lib/util/tevent_ntstatus.h"
 
 /*
  * Enhanced OS X and Netatalk compatibility
@@ -101,11 +104,12 @@ static int vfs_fruit_debug_level = DBGC_VFS;
  * REVIEW:
  * This is hokey, but what else can we do?
  */
+#define NETATALK_META_XATTR "org.netatalk.Metadata"
 #if defined(HAVE_ATTROPEN) || defined(FREEBSD)
-#define AFPINFO_EA_NETATALK "org.netatalk.Metadata"
+#define AFPINFO_EA_NETATALK NETATALK_META_XATTR
 #define AFPRESOURCE_EA_NETATALK "org.netatalk.ResourceFork"
 #else
-#define AFPINFO_EA_NETATALK "user.org.netatalk.Metadata"
+#define AFPINFO_EA_NETATALK "user." NETATALK_META_XATTR
 #define AFPRESOURCE_EA_NETATALK "user.org.netatalk.ResourceFork"
 #endif
 
@@ -121,6 +125,24 @@ struct fruit_config_data {
        enum fruit_meta meta;
        enum fruit_locking locking;
        enum fruit_encoding encoding;
+       bool use_aapl;          /* config from smb.conf */
+       bool nego_aapl;         /* client negotiated AAPL */
+       bool use_copyfile;
+       bool readdir_attr_enabled;
+       bool unix_info_enabled;
+       bool copyfile_enabled;
+       bool veto_appledouble;
+
+       /*
+        * Additional options, all enabled by default,
+        * possibly useful for analyzing performance. The associated
+        * operations with each of them may be expensive, so having
+        * the chance to disable them individually gives a chance
+        * tweaking the setup for the particular usecase.
+        */
+       bool readdir_attr_rsize;
+       bool readdir_attr_finder_info;
+       bool readdir_attr_max_access;
 };
 
 static const struct enum_list fruit_rsrc[] = {
@@ -510,7 +532,7 @@ static bool ad_pack(struct adouble *ad)
        offset += ADEDLEN_NENTRIES;
 
        for (eid = 0, nent = 0; eid < ADEID_MAX; eid++) {
-               if ((ad->ad_eid[eid].ade_off == 0)) {
+               if (ad->ad_eid[eid].ade_off == 0) {
                        /*
                         * ade_off is also used as indicator whether a
                         * specific entry is used or not
@@ -547,13 +569,13 @@ static bool ad_pack(struct adouble *ad)
        }
        RSSVAL(ad->ad_data, ADEDOFF_NENTRIES, nent);
 
-       return 0;
+       return true;
 }
 
 /**
  * Unpack an AppleDouble blob into a struct adoble
  **/
-static bool ad_unpack(struct adouble *ad, const int nentries)
+static bool ad_unpack(struct adouble *ad, const int nentries, size_t filesize)
 {
        size_t bufsize = talloc_get_size(ad->ad_data);
        int adentries, i;
@@ -596,11 +618,26 @@ static bool ad_unpack(struct adouble *ad, const int nentries)
                        return false;
                }
 
+               /*
+                * All entries other than the resource fork are
+                * expected to be read into the ad_data buffer, so
+                * ensure the specified offset is within that bound
+                */
                if ((off > bufsize) && (eid != ADEID_RFORK)) {
                        DEBUG(1, ("bogus eid %d: off: %" PRIu32 ", len: %" PRIu32 "\n",
                                  eid, off, len));
                        return false;
                }
+
+               /*
+                * All entries besides FinderInfo and resource fork
+                * must fit into the buffer. FinderInfo is special as
+                * it may be larger then the default 32 bytes (if it
+                * contains marshalled xattrs), but we will fixup that
+                * in ad_convert(). And the resource fork is never
+                * accessed directly by the ad_data buf (also see
+                * comment above) anyway.
+                */
                if ((eid != ADEID_RFORK) &&
                    (eid != ADEID_FINDERI) &&
                    ((off + len) > bufsize)) {
@@ -609,6 +646,48 @@ static bool ad_unpack(struct adouble *ad, const int nentries)
                        return false;
                }
 
+               /*
+                * That would be obviously broken
+                */
+               if (off > filesize) {
+                       DEBUG(1, ("bogus eid %d: off: %" PRIu32 ", len: %" PRIu32 "\n",
+                                 eid, off, len));
+                       return false;
+               }
+
+               /*
+                * Check for any entry that has its end beyond the
+                * filesize.
+                */
+               if (off + len < off) {
+                       DEBUG(1, ("offset wrap in eid %d: off: %" PRIu32
+                                 ", len: %" PRIu32 "\n",
+                                 eid, off, len));
+                       return false;
+
+               }
+               if (off + len > filesize) {
+                       /*
+                        * If this is the resource fork entry, we fix
+                        * up the length, for any other entry we bail
+                        * out.
+                        */
+                       if (eid != ADEID_RFORK) {
+                               DEBUG(1, ("bogus eid %d: off: %" PRIu32
+                                         ", len: %" PRIu32 "\n",
+                                         eid, off, len));
+                               return false;
+                       }
+
+                       /*
+                        * Fixup the resource fork entry by limiting
+                        * the size to entryoffset - filesize.
+                        */
+                       len = filesize - off;
+                       DEBUG(1, ("Limiting ADEID_RFORK: off: %" PRIu32
+                                 ", len: %" PRIu32 "\n", off, len));
+               }
+
                ad->ad_eid[eid].ade_off = off;
                ad->ad_eid[eid].ade_len = len;
        }
@@ -636,16 +715,18 @@ static int ad_convert(struct adouble *ad, int fd)
                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);
+       map = mmap(NULL, origlen, PROT_READ|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));
+       if (ad_getentrylen(ad, ADEID_RFORK) > 0) {
+               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,
@@ -703,7 +784,7 @@ static ssize_t ad_header_read_meta(struct adouble *ad, const char *path)
        }
 
        /* Now parse entries */
-       ok = ad_unpack(ad, ADEID_NUM_XATTR);
+       ok = ad_unpack(ad, ADEID_NUM_XATTR, AD_DATASZ_XATTR);
        if (!ok) {
                DEBUG(2, ("invalid AppleDouble metadata xattr\n"));
                errno = EINVAL;
@@ -755,11 +836,14 @@ static ssize_t ad_header_read_rsrc(struct adouble *ad, const char *path)
        struct adouble *meta_ad = NULL;
        SMB_STRUCT_STAT sbuf;
        bool ok;
-       int saved_errno;
+       int saved_errno = 0;
 
        SMB_VFS_HANDLE_GET_DATA(ad->ad_handle, config,
                                struct fruit_config_data, return -1);
 
+       /* Try rw first so we can use the fd in ad_convert() */
+       mode = O_RDWR;
+
        if (ad->ad_fsp && ad->ad_fsp->fh && (ad->ad_fsp->fh->fd != -1)) {
                fd = ad->ad_fsp->fh->fd;
        } else {
@@ -772,9 +856,6 @@ static ssize_t ad_header_read_rsrc(struct adouble *ad, const char *path)
                        }
                }
 
-               /* 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
@@ -817,10 +898,10 @@ static ssize_t ad_header_read_rsrc(struct adouble *ad, const char *path)
                        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);
+               len = sbuf.st_ex_size;
+               ad_setentrylen(ad, ADEID_RFORK, len);
        } else {
                /* FIXME: direct sys_pread(), don't have an fsp */
                len = sys_pread(fd, ad->ad_data, AD_DATASZ_DOT_UND, 0);
@@ -831,8 +912,16 @@ static ssize_t ad_header_read_rsrc(struct adouble *ad, const char *path)
                        goto exit;
                }
 
+               /* FIXME: direct sys_fstat(), we don't have an fsp */
+               rc = sys_fstat(fd, &sbuf,
+                              lp_fake_directory_create_times(
+                                      SNUM(ad->ad_handle->conn)));
+               if (rc != 0) {
+                       goto exit;
+               }
+
                /* Now parse entries */
-               ok = ad_unpack(ad, ADEID_NUM_DOT_UND);
+               ok = ad_unpack(ad, ADEID_NUM_DOT_UND, sbuf.st_ex_size);
                if (!ok) {
                        DEBUG(1, ("invalid AppleDouble ressource %s\n", path));
                        errno = EINVAL;
@@ -862,8 +951,9 @@ static ssize_t ad_header_read_rsrc(struct adouble *ad, const char *path)
                        /*
                         * Can't use ad_write() because we might not have a fsp
                         */
-                       rc = ad_pack(ad);
-                       if (rc != 0) {
+                       ok = ad_pack(ad);
+                       if (!ok) {
+                               rc = -1;
                                goto exit;
                        }
                        /* FIXME: direct sys_pwrite(), don't have an fsp */
@@ -1124,10 +1214,11 @@ static int ad_write(struct adouble *ad, const char *path)
 {
        int rc = 0;
        ssize_t len;
+       bool ok;
 
-       rc = ad_pack(ad);
-       if (rc != 0) {
-               goto exit;
+       ok = ad_pack(ad);
+       if (!ok) {
+               return -1;
        }
 
        switch (ad->ad_type) {
@@ -1251,6 +1342,28 @@ static int init_fruit_config(vfs_handle_struct *handle)
        }
        config->encoding = (enum fruit_encoding)enumval;
 
+       config->veto_appledouble = lp_parm_bool(
+               SNUM(handle->conn), FRUIT_PARAM_TYPE_NAME,
+               "veto_appledouble", true);
+
+       config->use_aapl = lp_parm_bool(
+               -1, FRUIT_PARAM_TYPE_NAME, "aapl", true);
+
+       config->unix_info_enabled = lp_parm_bool(
+               -1, FRUIT_PARAM_TYPE_NAME, "nfs_aces", true);
+
+       config->use_copyfile = lp_parm_bool(-1, FRUIT_PARAM_TYPE_NAME,
+                                          "copyfile", false);
+
+       config->readdir_attr_rsize = lp_parm_bool(
+               SNUM(handle->conn), "readdir_attr", "aapl_rsize", true);
+
+       config->readdir_attr_finder_info = lp_parm_bool(
+               SNUM(handle->conn), "readdir_attr", "aapl_finder_info", true);
+
+       config->readdir_attr_max_access = lp_parm_bool(
+               SNUM(handle->conn), "readdir_attr", "aapl_max_access", true);
+
        SMB_VFS_HANDLE_SET_DATA(handle, config,
                                NULL, struct fruit_config_data,
                                return -1);
@@ -1264,13 +1377,13 @@ static int init_fruit_config(vfs_handle_struct *handle)
 static int adouble_path(TALLOC_CTX *ctx, const char *path_in, char **path_out)
 {
        char *parent;
-       const char *basename;
+       const char *base;
 
-       if (!parent_dirname(ctx, path_in, &parent, &basename)) {
+       if (!parent_dirname(ctx, path_in, &parent, &base)) {
                return -1;
        }
 
-       *path_out = talloc_asprintf(ctx, "%s/._%s", parent, basename);
+       *path_out = talloc_asprintf(ctx, "%s/._%s", parent, base);
        if (*path_out == NULL) {
                return -1;
        }
@@ -1411,6 +1524,37 @@ static bool add_fruit_stream(TALLOC_CTX *mem_ctx, unsigned int *num_streams,
        return true;
 }
 
+static bool del_fruit_stream(TALLOC_CTX *mem_ctx, unsigned int *num_streams,
+                            struct stream_struct **streams,
+                            const char *name)
+{
+       struct stream_struct *tmp = *streams;
+       int i;
+
+       if (*num_streams == 0) {
+               return true;
+       }
+
+       for (i = 0; i < *num_streams; i++) {
+               if (strequal_m(tmp[i].name, name)) {
+                       break;
+               }
+       }
+
+       if (i == *num_streams) {
+               return true;
+       }
+
+       TALLOC_FREE(tmp[i].name);
+       if (*num_streams - 1 > i) {
+               memmove(&tmp[i], &tmp[i+1],
+                       (*num_streams - i - 1) * sizeof(struct stream_struct));
+       }
+
+       *num_streams -= 1;
+       return true;
+}
+
 static bool empty_finderinfo(const struct adouble *ad)
 {
 
@@ -1452,7 +1596,7 @@ static void update_btime(vfs_handle_struct *handle,
 /**
  * Map an access mask to a Netatalk single byte byte range lock
  **/
-static off_t access_to_netatalk_brl(enum apple_fork fork,
+static off_t access_to_netatalk_brl(enum apple_fork fork_type,
                                    uint32_t access_mask)
 {
        off_t offset;
@@ -1472,7 +1616,7 @@ static off_t access_to_netatalk_brl(enum apple_fork fork,
                break;
        }
 
-       if (fork == APPLE_FORK_RSRC) {
+       if (fork_type == APPLE_FORK_RSRC) {
                if (offset == AD_FILELOCK_OPEN_NONE) {
                        offset = AD_FILELOCK_RSRC_OPEN_NONE;
                } else {
@@ -1486,7 +1630,7 @@ static off_t access_to_netatalk_brl(enum apple_fork fork,
 /**
  * Map a deny mode to a Netatalk brl
  **/
-static off_t denymode_to_netatalk_brl(enum apple_fork fork,
+static off_t denymode_to_netatalk_brl(enum apple_fork fork_type,
                                      uint32_t deny_mode)
 {
        off_t offset;
@@ -1504,7 +1648,7 @@ static off_t denymode_to_netatalk_brl(enum apple_fork fork,
                smb_panic("denymode_to_netatalk_brl: bad deny mode\n");
        }
 
-       if (fork == APPLE_FORK_RSRC) {
+       if (fork_type == APPLE_FORK_RSRC) {
                offset += 2;
        }
 
@@ -1549,7 +1693,7 @@ static NTSTATUS fruit_check_access(vfs_handle_struct *handle,
        off_t off;
 
        /* FIXME: hardcoded data fork, add resource fork */
-       enum apple_fork fork = APPLE_FORK_DATA;
+       enum apple_fork fork_type = APPLE_FORK_DATA;
 
        DEBUG(10, ("fruit_check_access: %s, am: %s/%s, dm: %s/%s\n",
                  fsp_str_dbg(fsp),
@@ -1564,10 +1708,10 @@ static NTSTATUS fruit_check_access(vfs_handle_struct *handle,
        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));
+                       fsp, access_to_netatalk_brl(fork_type, FILE_READ_DATA));
 
                deny_read = test_netatalk_lock(
-                       fsp, denymode_to_netatalk_brl(fork, DENY_READ));
+                       fsp, denymode_to_netatalk_brl(fork_type, DENY_READ));
 
                DEBUG(10, ("read: %s, deny_write: %s\n",
                          open_for_reading == true ? "yes" : "no",
@@ -1580,7 +1724,7 @@ static NTSTATUS fruit_check_access(vfs_handle_struct *handle,
 
                /* Set locks */
                if (access_mask & FILE_READ_DATA) {
-                       off = access_to_netatalk_brl(fork, FILE_READ_DATA);
+                       off = access_to_netatalk_brl(fork_type, FILE_READ_DATA);
                        br_lck = do_lock(
                                handle->conn->sconn->msg_ctx, fsp,
                                fsp->op->global->open_persistent_id, 1, off,
@@ -1594,7 +1738,7 @@ static NTSTATUS fruit_check_access(vfs_handle_struct *handle,
                }
 
                if (deny_mode & DENY_READ) {
-                       off = denymode_to_netatalk_brl(fork, DENY_READ);
+                       off = denymode_to_netatalk_brl(fork_type, DENY_READ);
                        br_lck = do_lock(
                                handle->conn->sconn->msg_ctx, fsp,
                                fsp->op->global->open_persistent_id, 1, off,
@@ -1614,10 +1758,10 @@ static NTSTATUS fruit_check_access(vfs_handle_struct *handle,
        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));
+                       fsp, access_to_netatalk_brl(fork_type, FILE_WRITE_DATA));
 
                deny_write = test_netatalk_lock(
-                       fsp, denymode_to_netatalk_brl(fork, DENY_WRITE));
+                       fsp, denymode_to_netatalk_brl(fork_type, DENY_WRITE));
 
                DEBUG(10, ("write: %s, deny_write: %s\n",
                          open_for_writing == true ? "yes" : "no",
@@ -1630,7 +1774,7 @@ static NTSTATUS fruit_check_access(vfs_handle_struct *handle,
 
                /* Set locks */
                if (access_mask & FILE_WRITE_DATA) {
-                       off = access_to_netatalk_brl(fork, FILE_WRITE_DATA);
+                       off = access_to_netatalk_brl(fork_type, FILE_WRITE_DATA);
                        br_lck = do_lock(
                                handle->conn->sconn->msg_ctx, fsp,
                                fsp->op->global->open_persistent_id, 1, off,
@@ -1644,7 +1788,7 @@ static NTSTATUS fruit_check_access(vfs_handle_struct *handle,
 
                }
                if (deny_mode & DENY_WRITE) {
-                       off = denymode_to_netatalk_brl(fork, DENY_WRITE);
+                       off = denymode_to_netatalk_brl(fork_type, DENY_WRITE);
                        br_lck = do_lock(
                                handle->conn->sconn->msg_ctx, fsp,
                                fsp->op->global->open_persistent_id, 1, off,
@@ -1663,6 +1807,240 @@ static NTSTATUS fruit_check_access(vfs_handle_struct *handle,
        return status;
 }
 
+static NTSTATUS check_aapl(vfs_handle_struct *handle,
+                          struct smb_request *req,
+                          const struct smb2_create_blobs *in_context_blobs,
+                          struct smb2_create_blobs *out_context_blobs)
+{
+       struct fruit_config_data *config;
+       NTSTATUS status;
+       struct smb2_create_blob *aapl = NULL;
+       uint32_t cmd;
+       bool ok;
+       uint8_t p[16];
+       DATA_BLOB blob = data_blob_talloc(req, NULL, 0);
+       uint64_t req_bitmap, client_caps;
+       uint64_t server_caps = SMB2_CRTCTX_AAPL_UNIX_BASED;
+       smb_ucs2_t *model;
+       size_t modellen;
+
+       SMB_VFS_HANDLE_GET_DATA(handle, config, struct fruit_config_data,
+                               return NT_STATUS_UNSUCCESSFUL);
+
+       if (!config->use_aapl
+           || in_context_blobs == NULL
+           || out_context_blobs == NULL) {
+               return NT_STATUS_OK;
+       }
+
+       aapl = smb2_create_blob_find(in_context_blobs,
+                                    SMB2_CREATE_TAG_AAPL);
+       if (aapl == NULL) {
+               return NT_STATUS_OK;
+       }
+
+       if (aapl->data.length != 24) {
+               DEBUG(1, ("unexpected AAPL ctxt length: %ju\n",
+                         (uintmax_t)aapl->data.length));
+               return NT_STATUS_INVALID_PARAMETER;
+       }
+
+       cmd = IVAL(aapl->data.data, 0);
+       if (cmd != SMB2_CRTCTX_AAPL_SERVER_QUERY) {
+               DEBUG(1, ("unsupported AAPL cmd: %d\n", cmd));
+               return NT_STATUS_INVALID_PARAMETER;
+       }
+
+       req_bitmap = BVAL(aapl->data.data, 8);
+       client_caps = BVAL(aapl->data.data, 16);
+
+       SIVAL(p, 0, SMB2_CRTCTX_AAPL_SERVER_QUERY);
+       SIVAL(p, 4, 0);
+       SBVAL(p, 8, req_bitmap);
+       ok = data_blob_append(req, &blob, p, 16);
+       if (!ok) {
+               return NT_STATUS_UNSUCCESSFUL;
+       }
+
+       if (req_bitmap & SMB2_CRTCTX_AAPL_SERVER_CAPS) {
+               if ((client_caps & SMB2_CRTCTX_AAPL_SUPPORTS_READ_DIR_ATTR) &&
+                   (handle->conn->tcon->compat->fs_capabilities & FILE_NAMED_STREAMS)) {
+                       server_caps |= SMB2_CRTCTX_AAPL_SUPPORTS_READ_DIR_ATTR;
+                       config->readdir_attr_enabled = true;
+               }
+
+               if (config->use_copyfile) {
+                       server_caps |= SMB2_CRTCTX_AAPL_SUPPORTS_OSX_COPYFILE;
+                       config->copyfile_enabled = true;
+               }
+
+               /*
+                * The client doesn't set the flag, so we can't check
+                * for it and just set it unconditionally
+                */
+               if (config->unix_info_enabled) {
+                       server_caps |= SMB2_CRTCTX_AAPL_SUPPORTS_NFS_ACE;
+               }
+
+               SBVAL(p, 0, server_caps);
+               ok = data_blob_append(req, &blob, p, 8);
+               if (!ok) {
+                       return NT_STATUS_UNSUCCESSFUL;
+               }
+       }
+
+       if (req_bitmap & SMB2_CRTCTX_AAPL_VOLUME_CAPS) {
+               SBVAL(p, 0,
+                     lp_case_sensitive(SNUM(handle->conn->tcon->compat)) ?
+                     SMB2_CRTCTX_AAPL_CASE_SENSITIVE : 0);
+               ok = data_blob_append(req, &blob, p, 8);
+               if (!ok) {
+                       return NT_STATUS_UNSUCCESSFUL;
+               }
+       }
+
+       if (req_bitmap & SMB2_CRTCTX_AAPL_MODEL_INFO) {
+               ok = convert_string_talloc(req,
+                                          CH_UNIX, CH_UTF16LE,
+                                          "Samba", strlen("Samba"),
+                                          &model, &modellen);
+               if (!ok) {
+                       return NT_STATUS_UNSUCCESSFUL;
+               }
+
+               SIVAL(p, 0, 0);
+               SIVAL(p + 4, 0, modellen);
+               ok = data_blob_append(req, &blob, p, 8);
+               if (!ok) {
+                       talloc_free(model);
+                       return NT_STATUS_UNSUCCESSFUL;
+               }
+
+               ok = data_blob_append(req, &blob, model, modellen);
+               talloc_free(model);
+               if (!ok) {
+                       return NT_STATUS_UNSUCCESSFUL;
+               }
+       }
+
+       status = smb2_create_blob_add(out_context_blobs,
+                                     out_context_blobs,
+                                     SMB2_CREATE_TAG_AAPL,
+                                     blob);
+       if (NT_STATUS_IS_OK(status)) {
+               config->nego_aapl = true;
+       }
+
+       return status;
+}
+
+static NTSTATUS readdir_attr_macmeta(struct vfs_handle_struct *handle,
+                                    const struct smb_filename *smb_fname,
+                                    struct readdir_attr_data *attr_data)
+{
+       NTSTATUS status = NT_STATUS_OK;
+       uint32_t date_added;
+       struct adouble *ad = NULL;
+       struct fruit_config_data *config = NULL;
+
+       SMB_VFS_HANDLE_GET_DATA(handle, config,
+                               struct fruit_config_data,
+                               return NT_STATUS_UNSUCCESSFUL);
+
+
+       /* Ensure we return a default value in the creation_date field */
+       RSIVAL(&attr_data->attr_data.aapl.finder_info, 12, AD_DATE_START);
+
+       /*
+        * Resource fork length
+        */
+
+       if (config->readdir_attr_rsize) {
+               ad = ad_get(talloc_tos(), handle, smb_fname->base_name,
+                           ADOUBLE_RSRC);
+               if (ad) {
+                       attr_data->attr_data.aapl.rfork_size = ad_getentrylen(
+                               ad, ADEID_RFORK);
+                       TALLOC_FREE(ad);
+               }
+       }
+
+       /*
+        * FinderInfo
+        */
+
+       if (config->readdir_attr_finder_info) {
+               ad = ad_get(talloc_tos(), handle, smb_fname->base_name,
+                           ADOUBLE_META);
+               if (ad) {
+                       if (S_ISREG(smb_fname->st.st_ex_mode)) {
+                               /* finder_type */
+                               memcpy(&attr_data->attr_data.aapl.finder_info[0],
+                                      ad_entry(ad, ADEID_FINDERI), 4);
+
+                               /* finder_creator */
+                               memcpy(&attr_data->attr_data.aapl.finder_info[0] + 4,
+                                      ad_entry(ad, ADEID_FINDERI) + 4, 4);
+                       }
+
+                       /* finder_flags */
+                       memcpy(&attr_data->attr_data.aapl.finder_info[0] + 8,
+                              ad_entry(ad, ADEID_FINDERI) + 8, 2);
+
+                       /* finder_ext_flags */
+                       memcpy(&attr_data->attr_data.aapl.finder_info[0] + 10,
+                              ad_entry(ad, ADEID_FINDERI) + 24, 2);
+
+                       /* creation date */
+                       date_added = convert_time_t_to_uint32_t(
+                               smb_fname->st.st_ex_btime.tv_sec - AD_DATE_DELTA);
+                       RSIVAL(&attr_data->attr_data.aapl.finder_info[0], 12, date_added);
+
+                       TALLOC_FREE(ad);
+               }
+       }
+
+       TALLOC_FREE(ad);
+       return status;
+}
+
+/* Search MS NFS style ACE with UNIX mode */
+static NTSTATUS check_ms_nfs(vfs_handle_struct *handle,
+                            files_struct *fsp,
+                            const struct security_descriptor *psd,
+                            mode_t *pmode,
+                            bool *pdo_chmod)
+{
+       int i;
+       struct fruit_config_data *config = NULL;
+
+       *pdo_chmod = false;
+
+       SMB_VFS_HANDLE_GET_DATA(handle, config,
+                               struct fruit_config_data,
+                               return NT_STATUS_UNSUCCESSFUL);
+
+       if (psd->dacl == NULL || !config->unix_info_enabled) {
+               return NT_STATUS_OK;
+       }
+
+       for (i = 0; i < psd->dacl->num_aces; i++) {
+               if (dom_sid_compare_domain(
+                           &global_sid_Unix_NFS_Mode,
+                           &psd->dacl->aces[i].trustee) == 0) {
+                       *pmode = (mode_t)psd->dacl->aces[i].trustee.sub_auths[2];
+                       *pmode &= (S_IRWXU | S_IRWXG | S_IRWXO);
+                       *pdo_chmod = true;
+
+                       DEBUG(10, ("MS NFS chmod request %s, %04o\n",
+                                  fsp_str_dbg(fsp), (unsigned)(*pmode)));
+                       break;
+               }
+       }
+
+       return NT_STATUS_OK;
+}
+
 /****************************************************************************
  * VFS ops
  ****************************************************************************/
@@ -1682,26 +2060,6 @@ static int fruit_connect(vfs_handle_struct *handle,
                return rc;
        }
 
-       list = lp_veto_files(talloc_tos(), SNUM(handle->conn));
-
-       if (list) {
-               if (strstr(list, "/" ADOUBLE_NAME_PREFIX "*/") == NULL) {
-                       newlist = talloc_asprintf(
-                               list,
-                               "%s/" ADOUBLE_NAME_PREFIX "*/",
-                               list);
-                       lp_do_parameter(SNUM(handle->conn),
-                                       "veto files",
-                                       newlist);
-               }
-       } else {
-               lp_do_parameter(SNUM(handle->conn),
-                               "veto files",
-                               "/" ADOUBLE_NAME_PREFIX "*/");
-       }
-
-       TALLOC_FREE(list);
-
        rc = init_fruit_config(handle);
        if (rc != 0) {
                return rc;
@@ -1710,10 +2068,40 @@ static int fruit_connect(vfs_handle_struct *handle,
        SMB_VFS_HANDLE_GET_DATA(handle, config,
                                struct fruit_config_data, return -1);
 
+       if (config->veto_appledouble) {
+               list = lp_veto_files(talloc_tos(), SNUM(handle->conn));
+
+               if (list) {
+                       if (strstr(list, "/" ADOUBLE_NAME_PREFIX "*/") == NULL) {
+                               newlist = talloc_asprintf(
+                                       list,
+                                       "%s/" ADOUBLE_NAME_PREFIX "*/",
+                                       list);
+                               lp_do_parameter(SNUM(handle->conn),
+                                               "veto files",
+                                               newlist);
+                       }
+               } else {
+                       lp_do_parameter(SNUM(handle->conn),
+                                       "veto files",
+                                       "/" ADOUBLE_NAME_PREFIX "*/");
+               }
+
+               TALLOC_FREE(list);
+       }
+
        if (config->encoding == FRUIT_ENC_NATIVE) {
                lp_do_parameter(
                        SNUM(handle->conn),
                        "catia:mappings",
+                       "0x01:0xf001,0x02:0xf002,0x03:0xf003,0x04:0xf004,"
+                       "0x05:0xf005,0x06:0xf006,0x07:0xf007,0x08:0xf008,"
+                       "0x09:0xf009,0x0a:0xf00a,0x0b:0xf00b,0x0c:0xf00c,"
+                       "0x0d:0xf00d,0x0e:0xf00e,0x0f:0xf00f,0x10:0xf010,"
+                       "0x11:0xf011,0x12:0xf012,0x13:0xf013,0x14:0xf014,"
+                       "0x15:0xf015,0x16:0xf016,0x17:0xf017,0x18:0xf018,"
+                       "0x19:0xf019,0x1a:0xf01a,0x1b:0xf01b,0x1c:0xf01c,"
+                       "0x1d:0xf01d,0x1e:0xf01e,0x1f:0xf01f,"
                        "0x22:0xf020,0x2a:0xf021,0x3a:0xf022,0x3c:0xf023,"
                        "0x3e:0xf024,0x3f:0xf025,0x5c:0xf026,0x7c:0xf027,"
                        "0x0d:0xf00d");
@@ -2039,15 +2427,44 @@ static int fruit_unlink(vfs_handle_struct *handle,
 {
        int rc = -1;
        struct fruit_config_data *config = NULL;
-       char *adp = NULL;
-
-       if (!is_ntfs_stream_smb_fname(smb_fname)) {
-               return SMB_VFS_NEXT_UNLINK(handle, smb_fname);
-       }
 
        SMB_VFS_HANDLE_GET_DATA(handle, config,
                                struct fruit_config_data, return -1);
 
+       if (!is_ntfs_stream_smb_fname(smb_fname)) {
+               char *adp = NULL;
+
+               rc = SMB_VFS_NEXT_UNLINK(handle, smb_fname);
+               if (rc != 0) {
+                       return -1;
+               }
+
+               if (config->rsrc != FRUIT_RSRC_ADFILE) {
+                       return 0;
+               }
+
+               /*
+                * 0 byte resource fork streams are not listed by
+                * vfs_streaminfo, as a result stream cleanup/deletion of file
+                * deletion doesn't remove the resourcefork stream.
+                */
+               rc = adouble_path(talloc_tos(),
+                                 smb_fname->base_name, &adp);
+               if (rc != 0) {
+                       return -1;
+               }
+
+               /* FIXME: direct unlink(), missing smb_fname */
+               DBG_DEBUG("fruit_unlink: %s\n", adp);
+               rc = unlink(adp);
+               if ((rc == -1) && (errno == ENOENT)) {
+                       rc = 0;
+               }
+
+               TALLOC_FREE(adp);
+               return 0;
+       }
+
        if (is_afpinfo_stream(smb_fname)) {
                if (config->meta == FRUIT_META_STREAM) {
                        rc = SMB_VFS_NEXT_UNLINK(handle, smb_fname);
@@ -2056,29 +2473,19 @@ static int fruit_unlink(vfs_handle_struct *handle,
                                                 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;
        }
 
-       TALLOC_FREE(adp);
-       return rc;
+       if (is_afpresource_stream(smb_fname)) {
+               /* OS X ignores deletes on the AFP_Resource stream */
+               return 0;
+       }
+
+       return SMB_VFS_NEXT_UNLINK(handle, smb_fname);
+
+
+       return 0;
 }
 
 static int fruit_chmod(vfs_handle_struct *handle,
@@ -2169,11 +2576,13 @@ static int fruit_chown(vfs_handle_struct *handle,
        return rc;
 }
 
-static int fruit_rmdir(struct vfs_handle_struct *handle, const char *path)
+static int fruit_rmdir(struct vfs_handle_struct *handle,
+                       const struct smb_filename *smb_fname)
 {
        DIR *dh = NULL;
        struct dirent *de;
        struct fruit_config_data *config;
+       const char *path = smb_fname->base_name;
 
        SMB_VFS_HANDLE_GET_DATA(handle, config,
                                struct fruit_config_data, return -1);
@@ -2211,7 +2620,7 @@ exit_rmdir:
        if (dh) {
                closedir(dh);
        }
-       return SMB_VFS_NEXT_RMDIR(handle, path);
+       return SMB_VFS_NEXT_RMDIR(handle, smb_fname);
 }
 
 static ssize_t fruit_pread(vfs_handle_struct *handle,
@@ -2223,7 +2632,7 @@ static ssize_t fruit_pread(vfs_handle_struct *handle,
                handle, fsp);
        struct fruit_config_data *config = NULL;
        AfpInfo *ai = NULL;
-       ssize_t len;
+       ssize_t len = -1;
        char *name = NULL;
        char *tmp_base_name = NULL;
        NTSTATUS status;
@@ -2243,7 +2652,13 @@ static ssize_t fruit_pread(vfs_handle_struct *handle,
                                        fsp->base_fsp->fsp_name->base_name,
                                        vfs_translate_to_unix,
                                        talloc_tos(), &name);
-       if (!NT_STATUS_IS_OK(status)) {
+       if (NT_STATUS_EQUAL(status, NT_STATUS_NONE_MAPPED)) {
+               name = talloc_strdup(talloc_tos(), tmp_base_name);
+               if (name == NULL) {
+                       rc = -1;
+                       goto exit;
+               }
+       } else if (!NT_STATUS_IS_OK(status)) {
                errno = map_errno_from_nt_status(status);
                rc = -1;
                goto exit;
@@ -2265,6 +2680,23 @@ static ssize_t fruit_pread(vfs_handle_struct *handle,
        }
 
        if (ad->ad_type == ADOUBLE_META) {
+               char afpinfo_buf[AFP_INFO_SIZE];
+               size_t to_return;
+
+               /*
+                * OS X has a off-by-1 error in the offset calculation, so we're
+                * bug compatible here. It won't hurt, as any relevant real
+                * world read requests from the AFP_AfpInfo stream will be
+                * offset=0 n=60. offset is ignored anyway, see below.
+                */
+               if ((offset < 0) || (offset >= AFP_INFO_SIZE + 1)) {
+                       len = 0;
+                       rc = 0;
+                       goto exit;
+               }
+
+               to_return = MIN(n, AFP_INFO_SIZE);
+
                ai = afpinfo_new(talloc_tos());
                if (ai == NULL) {
                        rc = -1;
@@ -2280,11 +2712,17 @@ static ssize_t fruit_pread(vfs_handle_struct *handle,
                memcpy(&ai->afpi_FinderInfo[0],
                       ad_entry(ad, ADEID_FINDERI),
                       ADEDLEN_FINDERI);
-               len = afpinfo_pack(ai, data);
+               len = afpinfo_pack(ai, afpinfo_buf);
                if (len != AFP_INFO_SIZE) {
                        rc = -1;
                        goto exit;
                }
+
+               /*
+                * OS X ignores offset when reading from AFP_AfpInfo stream!
+                */
+               memcpy(data, afpinfo_buf, to_return);
+               len = to_return;
        } else {
                len = SMB_VFS_NEXT_PREAD(
                        handle, fsp, data, n,
@@ -2333,7 +2771,13 @@ static ssize_t fruit_pwrite(vfs_handle_struct *handle,
                                        fsp->base_fsp->fsp_name->base_name,
                                        vfs_translate_to_unix,
                                        talloc_tos(), &name);
-       if (!NT_STATUS_IS_OK(status)) {
+       if (NT_STATUS_EQUAL(status, NT_STATUS_NONE_MAPPED)) {
+               name = talloc_strdup(talloc_tos(), tmp_base_name);
+               if (name == NULL) {
+                       rc = -1;
+                       goto exit;
+               }
+       } else if (!NT_STATUS_IS_OK(status)) {
                errno = map_errno_from_nt_status(status);
                rc = -1;
                goto exit;
@@ -2368,7 +2812,24 @@ static ssize_t fruit_pwrite(vfs_handle_struct *handle,
                }
                memcpy(ad_entry(ad, ADEID_FINDERI),
                       &ai->afpi_FinderInfo[0], ADEDLEN_FINDERI);
-               rc = ad_write(ad, name);
+               if (empty_finderinfo(ad)) {
+                       /* Discard metadata */
+                       if (config->meta == FRUIT_META_STREAM) {
+                               rc = SMB_VFS_FTRUNCATE(fsp, 0);
+                       } else {
+                               rc = SMB_VFS_REMOVEXATTR(handle->conn,
+                                                        fsp->fsp_name->base_name,
+                                                        AFPINFO_EA_NETATALK);
+                       }
+                       if (rc != 0 && errno != ENOENT && errno != ENOATTR) {
+                               DBG_WARNING("Can't delete metadata for %s: %s\n",
+                                           fsp->fsp_name->base_name, strerror(errno));
+                               goto exit;
+                       }
+                       rc = 0;
+                       goto exit;
+               }
+               rc = ad_write(ad, name);
        } else {
                len = SMB_VFS_NEXT_PWRITE(handle, fsp, data, n,
                                    offset + ad_getentryoff(ad, ADEID_RFORK));
@@ -2426,6 +2887,17 @@ static int fruit_stat_meta(vfs_handle_struct *handle,
                           struct smb_filename *smb_fname,
                           bool follow_links)
 {
+       struct adouble *ad = NULL;
+
+       ad = ad_get(talloc_tos(), handle, smb_fname->base_name, ADOUBLE_META);
+       if (ad == NULL) {
+               DBG_INFO("fruit_stat_meta %s: %s\n",
+                        smb_fname_str_dbg(smb_fname), strerror(errno));
+               errno = ENOENT;
+               return -1;
+       }
+       TALLOC_FREE(ad);
+
        /* Populate the stat struct with info from the base file. */
        if (fruit_stat_base(handle, smb_fname, follow_links) == -1) {
                return -1;
@@ -2606,7 +3078,7 @@ static int fruit_fstat(vfs_handle_struct *handle, files_struct *fsp,
                   smb_fname_str_dbg(fsp->fsp_name)));
 
        if (fsp->base_fsp) {
-               tmp_base_name = fsp->fsp_name->base_name;
+               tmp_base_name = fsp->base_fsp->fsp_name->base_name;
                /* fsp_name is not converted with vfs_catia */
                status = SMB_VFS_TRANSLATE_NAME(
                        handle->conn,
@@ -2614,7 +3086,13 @@ static int fruit_fstat(vfs_handle_struct *handle, files_struct *fsp,
                        vfs_translate_to_unix,
                        talloc_tos(), &name);
 
-               if (!NT_STATUS_IS_OK(status)) {
+               if (NT_STATUS_EQUAL(status, NT_STATUS_NONE_MAPPED)) {
+                       name = talloc_strdup(talloc_tos(), tmp_base_name);
+                       if (name == NULL) {
+                               rc = -1;
+                               goto exit;
+                       }
+               } else if (!NT_STATUS_IS_OK(status)) {
                        errno = map_errno_from_nt_status(status);
                        rc = -1;
                        goto exit;
@@ -2673,6 +3151,7 @@ static NTSTATUS fruit_streaminfo(vfs_handle_struct *handle,
        struct fruit_config_data *config = NULL;
        struct smb_filename *smb_fname = NULL;
        struct adouble *ad = NULL;
+       NTSTATUS status;
 
        SMB_VFS_HANDLE_GET_DATA(handle, config, struct fruit_config_data,
                                return NT_STATUS_UNSUCCESSFUL);
@@ -2703,7 +3182,7 @@ static NTSTATUS fruit_streaminfo(vfs_handle_struct *handle,
        if (config->rsrc != FRUIT_RSRC_STREAM) {
                ad = ad_get(talloc_tos(), handle, smb_fname->base_name,
                            ADOUBLE_RSRC);
-               if (ad) {
+               if (ad && (ad_getentrylen(ad, ADEID_RFORK) > 0)) {
                        if (!add_fruit_stream(
                                    mem_ctx, pnum_streams, pstreams,
                                    AFPRESOURCE_STREAM_NAME,
@@ -2721,8 +3200,23 @@ static NTSTATUS fruit_streaminfo(vfs_handle_struct *handle,
 
        TALLOC_FREE(smb_fname);
 
-       return SMB_VFS_NEXT_STREAMINFO(handle, fsp, fname, mem_ctx,
-                                      pnum_streams, pstreams);
+       status = SMB_VFS_NEXT_STREAMINFO(handle, fsp, fname, mem_ctx,
+                                        pnum_streams, pstreams);
+       if (!NT_STATUS_IS_OK(status)) {
+               return status;
+       }
+
+       if (config->meta == FRUIT_META_NETATALK) {
+               /* Remove the Netatalk xattr from the list */
+               if (!del_fruit_stream(mem_ctx, pnum_streams, pstreams,
+                                     ":" NETATALK_META_XATTR ":$DATA")) {
+                               TALLOC_FREE(ad);
+                               TALLOC_FREE(smb_fname);
+                               return NT_STATUS_NO_MEMORY;
+               }
+       }
+
+       return NT_STATUS_OK;
 }
 
 static int fruit_ntimes(vfs_handle_struct *handle,
@@ -2761,7 +3255,7 @@ exit:
 
 static int fruit_fallocate(struct vfs_handle_struct *handle,
                           struct files_struct *fsp,
-                          enum vfs_fallocate_mode mode,
+                          uint32_t mode,
                           off_t offset,
                           off_t len)
 {
@@ -2773,11 +3267,72 @@ static int fruit_fallocate(struct vfs_handle_struct *handle,
        }
 
        if (!fruit_fsp_recheck(ad, fsp)) {
-               return errno;
+               return -1;
        }
 
        /* Let the pwrite code path handle it. */
-       return ENOSYS;
+       errno = ENOSYS;
+       return -1;
+}
+
+static int fruit_ftruncate_meta(struct vfs_handle_struct *handle,
+                               struct files_struct *fsp,
+                               off_t offset,
+                               struct adouble *ad)
+{
+       struct fruit_config_data *config;
+
+       SMB_VFS_HANDLE_GET_DATA(handle, config,
+                               struct fruit_config_data, return -1);
+
+       if (offset > 60) {
+               DBG_WARNING("ftruncate %s to %jd",
+                           fsp_str_dbg(fsp), (intmax_t)offset);
+               /* OS X returns NT_STATUS_ALLOTTED_SPACE_EXCEEDED  */
+               errno = EOVERFLOW;
+               return -1;
+       }
+
+       DBG_WARNING("ignoring ftruncate %s to %jd",
+                   fsp_str_dbg(fsp), (intmax_t)offset);
+       /* OS X returns success but does nothing  */
+       return 0;
+}
+
+static int fruit_ftruncate_rsrc(struct vfs_handle_struct *handle,
+                               struct files_struct *fsp,
+                               off_t offset,
+                               struct adouble *ad)
+{
+       int rc;
+       struct fruit_config_data *config;
+
+       SMB_VFS_HANDLE_GET_DATA(handle, config,
+                               struct fruit_config_data, return -1);
+
+       if (config->rsrc == FRUIT_RSRC_XATTR && offset == 0) {
+               return SMB_VFS_FREMOVEXATTR(fsp,
+                                           AFPRESOURCE_EA_NETATALK);
+       }
+
+       rc = SMB_VFS_NEXT_FTRUNCATE(
+               handle, fsp,
+               offset + ad_getentryoff(ad, ADEID_RFORK));
+       if (rc != 0) {
+               return -1;
+       }
+
+       if (config->rsrc == FRUIT_RSRC_ADFILE) {
+               ad_setentrylen(ad, ADEID_RFORK, offset);
+               rc = ad_write(ad, NULL);
+               if (rc != 0) {
+                       return -1;
+               }
+               DEBUG(10, ("fruit_ftruncate_rsrc file %s offset %jd\n",
+                          fsp_str_dbg(fsp), (intmax_t)offset));
+       }
+
+       return 0;
 }
 
 static int fruit_ftruncate(struct vfs_handle_struct *handle,
@@ -2787,10 +3342,9 @@ static int fruit_ftruncate(struct vfs_handle_struct *handle,
        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));
+       DBG_DEBUG("fruit_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);
@@ -2800,40 +3354,15 @@ static int fruit_ftruncate(struct vfs_handle_struct *handle,
                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);
-               }
+               rc = fruit_ftruncate_meta(handle, fsp, offset, ad);
                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));
-                       if (rc != 0) {
-                               return -1;
-                       }
-                       ad_setentrylen(ad, ADEID_RFORK, offset);
-                       rc = ad_write(ad, NULL);
-                       if (rc != 0) {
-                               return -1;
-                       }
-               }
+               rc = fruit_ftruncate_rsrc(handle, fsp, offset, ad);
                break;
+
        default:
                return -1;
        }
@@ -2857,10 +3386,21 @@ static NTSTATUS fruit_create_file(vfs_handle_struct *handle,
                                  struct security_descriptor *sd,
                                  struct ea_list *ea_list,
                                  files_struct **result,
-                                 int *pinfo)
+                                 int *pinfo,
+                                 const struct smb2_create_blobs *in_context_blobs,
+                                 struct smb2_create_blobs *out_context_blobs)
 {
        NTSTATUS status;
        struct fruit_config_data *config = NULL;
+       files_struct *fsp = NULL;
+
+       status = check_aapl(handle, req, in_context_blobs, out_context_blobs);
+       if (!NT_STATUS_IS_OK(status)) {
+               goto fail;
+       }
+
+       SMB_VFS_HANDLE_GET_DATA(handle, config, struct fruit_config_data,
+                               return NT_STATUS_UNSUCCESSFUL);
 
        status = SMB_VFS_NEXT_CREATE_FILE(
                handle, req, root_dir_fid, smb_fname,
@@ -2870,21 +3410,54 @@ static NTSTATUS fruit_create_file(vfs_handle_struct *handle,
                lease,
                allocation_size, private_flags,
                sd, ea_list, result,
-               pinfo);
-
+               pinfo, in_context_blobs, out_context_blobs);
        if (!NT_STATUS_IS_OK(status)) {
                return status;
        }
 
+       fsp = *result;
+
+       if (config->nego_aapl) {
+               if (config->copyfile_enabled) {
+                       /*
+                        * Set a flag in the fsp. Gets used in
+                        * copychunk to check whether the special
+                        * Apple copyfile semantics for copychunk
+                        * should be allowed in a copychunk request
+                        * with a count of 0.
+                        */
+                       fsp->aapl_copyfile_supported = true;
+               }
+
+               if (fsp->is_directory) {
+                       /*
+                        * Enable POSIX directory rename behaviour
+                        */
+                       fsp->posix_flags |= FSP_POSIX_FLAGS_RENAME;
+               }
+       }
+
+       /*
+        * If this is a plain open for existing files, opening an 0
+        * byte size resource fork MUST fail with
+        * NT_STATUS_OBJECT_NAME_NOT_FOUND.
+        *
+        * Cf the vfs_fruit torture tests in test_rfork_create().
+        */
+       if (is_afpresource_stream(fsp->fsp_name) &&
+           create_disposition == FILE_OPEN)
+       {
+               if (fsp->fsp_name->st.st_ex_size == 0) {
+                       status = NT_STATUS_OBJECT_NAME_NOT_FOUND;
+                       goto fail;
+               }
+       }
+
        if (is_ntfs_stream_smb_fname(smb_fname)
-           || (*result == NULL)
-           || ((*result)->is_directory)) {
+           || fsp->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,
@@ -2898,16 +3471,392 @@ static NTSTATUS fruit_create_file(vfs_handle_struct *handle,
        return status;
 
 fail:
-       DEBUG(1, ("fruit_create_file: %s\n", nt_errstr(status)));
+       DEBUG(10, ("fruit_create_file: %s\n", nt_errstr(status)));
 
-       if (*result) {
-               close_file(req, *result, ERROR_CLOSE);
-               *result = NULL;
+       if (fsp) {
+               close_file(req, fsp, ERROR_CLOSE);
+               *result = fsp = NULL;
        }
 
        return status;
 }
 
+static NTSTATUS fruit_readdir_attr(struct vfs_handle_struct *handle,
+                                  const struct smb_filename *fname,
+                                  TALLOC_CTX *mem_ctx,
+                                  struct readdir_attr_data **pattr_data)
+{
+       struct fruit_config_data *config = NULL;
+       struct readdir_attr_data *attr_data;
+       NTSTATUS status;
+
+       SMB_VFS_HANDLE_GET_DATA(handle, config,
+                               struct fruit_config_data,
+                               return NT_STATUS_UNSUCCESSFUL);
+
+       if (!config->use_aapl) {
+               return SMB_VFS_NEXT_READDIR_ATTR(handle, fname, mem_ctx, pattr_data);
+       }
+
+       DEBUG(10, ("fruit_readdir_attr %s\n", fname->base_name));
+
+       *pattr_data = talloc_zero(mem_ctx, struct readdir_attr_data);
+       if (*pattr_data == NULL) {
+               return NT_STATUS_UNSUCCESSFUL;
+       }
+       attr_data = *pattr_data;
+       attr_data->type = RDATTR_AAPL;
+
+       /*
+        * Mac metadata: compressed FinderInfo, resource fork length
+        * and creation date
+        */
+       status = readdir_attr_macmeta(handle, fname, attr_data);
+       if (!NT_STATUS_IS_OK(status)) {
+               /*
+                * Error handling is tricky: if we return failure from
+                * this function, the corresponding directory entry
+                * will to be passed to the client, so we really just
+                * want to error out on fatal errors.
+                */
+               if  (!NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED)) {
+                       goto fail;
+               }
+       }
+
+       /*
+        * UNIX mode
+        */
+       if (config->unix_info_enabled) {
+               attr_data->attr_data.aapl.unix_mode = fname->st.st_ex_mode;
+       }
+
+       /*
+        * max_access
+        */
+       if (!config->readdir_attr_max_access) {
+               attr_data->attr_data.aapl.max_access = FILE_GENERIC_ALL;
+       } else {
+               status = smbd_calculate_access_mask(
+                       handle->conn,
+                       fname,
+                       false,
+                       SEC_FLAG_MAXIMUM_ALLOWED,
+                       &attr_data->attr_data.aapl.max_access);
+               if (!NT_STATUS_IS_OK(status)) {
+                       goto fail;
+               }
+       }
+
+       return NT_STATUS_OK;
+
+fail:
+       DEBUG(1, ("fruit_readdir_attr %s, error: %s\n",
+                 fname->base_name, nt_errstr(status)));
+       TALLOC_FREE(*pattr_data);
+       return status;
+}
+
+static NTSTATUS fruit_fget_nt_acl(vfs_handle_struct *handle,
+                                 files_struct *fsp,
+                                 uint32_t security_info,
+                                 TALLOC_CTX *mem_ctx,
+                                 struct security_descriptor **ppdesc)
+{
+       NTSTATUS status;
+       struct security_ace ace;
+       struct dom_sid sid;
+       struct fruit_config_data *config;
+
+       SMB_VFS_HANDLE_GET_DATA(handle, config,
+                               struct fruit_config_data,
+                               return NT_STATUS_UNSUCCESSFUL);
+
+       status = SMB_VFS_NEXT_FGET_NT_ACL(handle, fsp, security_info,
+                                         mem_ctx, ppdesc);
+       if (!NT_STATUS_IS_OK(status)) {
+               return status;
+       }
+
+       /*
+        * Add MS NFS style ACEs with uid, gid and mode
+        */
+       if (!config->unix_info_enabled) {
+               return NT_STATUS_OK;
+       }
+
+       /* MS NFS style mode */
+       sid_compose(&sid, &global_sid_Unix_NFS_Mode, fsp->fsp_name->st.st_ex_mode);
+       init_sec_ace(&ace, &sid, SEC_ACE_TYPE_ACCESS_DENIED, 0, 0);
+       status = security_descriptor_dacl_add(*ppdesc, &ace);
+       if (!NT_STATUS_IS_OK(status)) {
+               DEBUG(1,("failed to add MS NFS style ACE\n"));
+               return status;
+       }
+
+       /* MS NFS style uid */
+       sid_compose(&sid, &global_sid_Unix_NFS_Users, fsp->fsp_name->st.st_ex_uid);
+       init_sec_ace(&ace, &sid, SEC_ACE_TYPE_ACCESS_DENIED, 0, 0);
+       status = security_descriptor_dacl_add(*ppdesc, &ace);
+       if (!NT_STATUS_IS_OK(status)) {
+               DEBUG(1,("failed to add MS NFS style ACE\n"));
+               return status;
+       }
+
+       /* MS NFS style gid */
+       sid_compose(&sid, &global_sid_Unix_NFS_Groups, fsp->fsp_name->st.st_ex_gid);
+       init_sec_ace(&ace, &sid, SEC_ACE_TYPE_ACCESS_DENIED, 0, 0);
+       status = security_descriptor_dacl_add(*ppdesc, &ace);
+       if (!NT_STATUS_IS_OK(status)) {
+               DEBUG(1,("failed to add MS NFS style ACE\n"));
+               return status;
+       }
+
+       return NT_STATUS_OK;
+}
+
+static NTSTATUS fruit_fset_nt_acl(vfs_handle_struct *handle,
+                                 files_struct *fsp,
+                                 uint32_t security_info_sent,
+                                 const struct security_descriptor *psd)
+{
+       NTSTATUS status;
+       bool do_chmod;
+       mode_t ms_nfs_mode;
+       int result;
+
+       DBG_DEBUG("fruit_fset_nt_acl: %s\n", fsp_str_dbg(fsp));
+
+       status = check_ms_nfs(handle, fsp, psd, &ms_nfs_mode, &do_chmod);
+       if (!NT_STATUS_IS_OK(status)) {
+               DEBUG(1, ("fruit_fset_nt_acl: check_ms_nfs failed%s\n", fsp_str_dbg(fsp)));
+               return status;
+       }
+
+       status = SMB_VFS_NEXT_FSET_NT_ACL(handle, fsp, security_info_sent, psd);
+       if (!NT_STATUS_IS_OK(status)) {
+               DEBUG(1, ("fruit_fset_nt_acl: SMB_VFS_NEXT_FSET_NT_ACL failed%s\n", fsp_str_dbg(fsp)));
+               return status;
+       }
+
+       if (do_chmod) {
+               if (fsp->fh->fd != -1) {
+                       result = SMB_VFS_FCHMOD(fsp, ms_nfs_mode);
+               } else {
+                       result = SMB_VFS_CHMOD(fsp->conn,
+                                              fsp->fsp_name->base_name,
+                                              ms_nfs_mode);
+               }
+
+               if (result != 0) {
+                       DEBUG(1, ("chmod: %s, result: %d, %04o error %s\n", fsp_str_dbg(fsp),
+                                 result, (unsigned)ms_nfs_mode,
+                                 strerror(errno)));
+                       status = map_nt_error_from_unix(errno);
+                       return status;
+               }
+       }
+
+       return NT_STATUS_OK;
+}
+
+struct fruit_copy_chunk_state {
+       struct vfs_handle_struct *handle;
+       off_t copied;
+       struct files_struct *src_fsp;
+       struct files_struct *dst_fsp;
+       bool is_copyfile;
+};
+
+static void fruit_copy_chunk_done(struct tevent_req *subreq);
+static struct tevent_req *fruit_copy_chunk_send(struct vfs_handle_struct *handle,
+                                               TALLOC_CTX *mem_ctx,
+                                               struct tevent_context *ev,
+                                               struct files_struct *src_fsp,
+                                               off_t src_off,
+                                               struct files_struct *dest_fsp,
+                                               off_t dest_off,
+                                               off_t num)
+{
+       struct tevent_req *req, *subreq;
+       struct fruit_copy_chunk_state *fruit_copy_chunk_state;
+       NTSTATUS status;
+       struct fruit_config_data *config;
+       off_t to_copy = num;
+
+       DEBUG(10,("soff: %ju, doff: %ju, len: %ju\n",
+                 (uintmax_t)src_off, (uintmax_t)dest_off, (uintmax_t)num));
+
+       SMB_VFS_HANDLE_GET_DATA(handle, config,
+                               struct fruit_config_data,
+                               return NULL);
+
+       req = tevent_req_create(mem_ctx, &fruit_copy_chunk_state,
+                               struct fruit_copy_chunk_state);
+       if (req == NULL) {
+               return NULL;
+       }
+       fruit_copy_chunk_state->handle = handle;
+       fruit_copy_chunk_state->src_fsp = src_fsp;
+       fruit_copy_chunk_state->dst_fsp = dest_fsp;
+
+       /*
+        * Check if this a OS X copyfile style copychunk request with
+        * a requested chunk count of 0 that was translated to a
+        * copy_chunk_send VFS call overloading the parameters src_off
+        * = dest_off = num = 0.
+        */
+       if ((src_off == 0) && (dest_off == 0) && (num == 0) &&
+           src_fsp->aapl_copyfile_supported &&
+           dest_fsp->aapl_copyfile_supported)
+       {
+               status = vfs_stat_fsp(src_fsp);
+               if (tevent_req_nterror(req, status)) {
+                       return tevent_req_post(req, ev);
+               }
+
+               to_copy = src_fsp->fsp_name->st.st_ex_size;
+               fruit_copy_chunk_state->is_copyfile = true;
+       }
+
+       subreq = SMB_VFS_NEXT_COPY_CHUNK_SEND(handle,
+                                             mem_ctx,
+                                             ev,
+                                             src_fsp,
+                                             src_off,
+                                             dest_fsp,
+                                             dest_off,
+                                             to_copy);
+       if (tevent_req_nomem(subreq, req)) {
+               return tevent_req_post(req, ev);
+       }
+
+       tevent_req_set_callback(subreq, fruit_copy_chunk_done, req);
+       return req;
+}
+
+static void fruit_copy_chunk_done(struct tevent_req *subreq)
+{
+       struct tevent_req *req = tevent_req_callback_data(
+               subreq, struct tevent_req);
+       struct fruit_copy_chunk_state *state = tevent_req_data(
+               req, struct fruit_copy_chunk_state);
+       NTSTATUS status;
+       unsigned int num_streams = 0;
+       struct stream_struct *streams = NULL;
+       int i;
+       struct smb_filename *src_fname_tmp = NULL;
+       struct smb_filename *dst_fname_tmp = NULL;
+
+       status = SMB_VFS_NEXT_COPY_CHUNK_RECV(state->handle,
+                                             subreq,
+                                             &state->copied);
+       TALLOC_FREE(subreq);
+       if (tevent_req_nterror(req, status)) {
+               return;
+       }
+
+       if (!state->is_copyfile) {
+               tevent_req_done(req);
+               return;
+       }
+
+       /*
+        * Now copy all reamining streams. We know the share supports
+        * streams, because we're in vfs_fruit. We don't do this async
+        * because streams are few and small.
+        */
+       status = vfs_streaminfo(state->handle->conn, NULL,
+                               state->src_fsp->fsp_name->base_name,
+                               req, &num_streams, &streams);
+       if (tevent_req_nterror(req, status)) {
+               return;
+       }
+
+       if (num_streams == 1) {
+               /* There is always one stream, ::$DATA. */
+               tevent_req_done(req);
+               return;
+       }
+
+       for (i = 0; i < num_streams; i++) {
+               DEBUG(10, ("%s: stream: '%s'/%ju\n",
+                          __func__, streams[i].name,
+                          (uintmax_t)streams[i].size));
+
+               src_fname_tmp = synthetic_smb_fname(
+                       req,
+                       state->src_fsp->fsp_name->base_name,
+                       streams[i].name,
+                       NULL);
+               if (tevent_req_nomem(src_fname_tmp, req)) {
+                       return;
+               }
+
+               if (is_ntfs_default_stream_smb_fname(src_fname_tmp)) {
+                       TALLOC_FREE(src_fname_tmp);
+                       continue;
+               }
+
+               dst_fname_tmp = synthetic_smb_fname(
+                       req,
+                       state->dst_fsp->fsp_name->base_name,
+                       streams[i].name,
+                       NULL);
+               if (tevent_req_nomem(dst_fname_tmp, req)) {
+                       TALLOC_FREE(src_fname_tmp);
+                       return;
+               }
+
+               status = copy_file(req,
+                                  state->handle->conn,
+                                  src_fname_tmp,
+                                  dst_fname_tmp,
+                                  OPENX_FILE_CREATE_IF_NOT_EXIST,
+                                  0, false);
+               if (!NT_STATUS_IS_OK(status)) {
+                       DEBUG(1, ("%s: copy %s to %s failed: %s\n", __func__,
+                                 smb_fname_str_dbg(src_fname_tmp),
+                                 smb_fname_str_dbg(dst_fname_tmp),
+                                 nt_errstr(status)));
+                       TALLOC_FREE(src_fname_tmp);
+                       TALLOC_FREE(dst_fname_tmp);
+                       tevent_req_nterror(req, status);
+                       return;
+               }
+
+               TALLOC_FREE(src_fname_tmp);
+               TALLOC_FREE(dst_fname_tmp);
+       }
+
+       TALLOC_FREE(streams);
+       TALLOC_FREE(src_fname_tmp);
+       TALLOC_FREE(dst_fname_tmp);
+       tevent_req_done(req);
+}
+
+static NTSTATUS fruit_copy_chunk_recv(struct vfs_handle_struct *handle,
+                                     struct tevent_req *req,
+                                     off_t *copied)
+{
+       struct fruit_copy_chunk_state *fruit_copy_chunk_state = tevent_req_data(
+               req, struct fruit_copy_chunk_state);
+       NTSTATUS status;
+
+       if (tevent_req_is_nterror(req, &status)) {
+               DEBUG(1, ("server side copy chunk failed: %s\n",
+                         nt_errstr(status)));
+               *copied = 0;
+               tevent_req_received(req);
+               return status;
+       }
+
+       *copied = fruit_copy_chunk_state->copied;
+       tevent_req_received(req);
+
+       return NT_STATUS_OK;
+}
+
 static struct vfs_fn_pointers vfs_fruit_fns = {
        .connect_fn = fruit_connect,
 
@@ -2925,10 +3874,16 @@ static struct vfs_fn_pointers vfs_fruit_fns = {
        .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,
+       .readdir_attr_fn = fruit_readdir_attr,
+       .copy_chunk_send_fn = fruit_copy_chunk_send,
+       .copy_chunk_recv_fn = fruit_copy_chunk_recv,
+
+       /* NT ACL operations */
+       .fget_nt_acl_fn = fruit_fget_nt_acl,
+       .fset_nt_acl_fn = fruit_fset_nt_acl,
 };
 
 NTSTATUS vfs_fruit_init(void);