[PATCH] Fix nasty vfs_fruit bug 13646

Ralph Böhme slow at samba.org
Tue Oct 23 15:54:08 UTC 2018


Hi!

Attached is a somewhat largish patchset to fix bug 13646.

It got this big as I'm expanding existing tests and adding a new large one, 
trying very hard to test all possible combinations of resetting the a stream on 
one handle and testing the outcome on the same and different handles.

At the base this patchset fixes the problem introduced by the patches for bug 
13441. There I added a change to unlink a stream in the VFS ftruncate function 
if offset=0.

As SMB_VFS_FTRUNCATE with offset=0 may also be called at create time with create 
dispostion OVERWRITE, this does more bad then good and caused real world 
application problems like MS Excel not being able to save. :/

Simply reverting the change from bug 13441 was not really an option as that 
would have reintroduced the problems 13441 was trying to fix.

Hopefully this patchset does a better job. I've therefor added a new large test 
that covers the subtle cases and I've did some manual end-to-end testing with 
Excel, Word, Photoshop and Finder Tags.

Please review&push if happy. Thanks!

CI: https://gitlab.com/samba-team/devel/samba/pipelines/34033409

Oh, and please apply the fruit patchsets in the order of appearance on the list:

[PATCH] Follow-up fix for bug 13649
[PATCH] Add optional AppleDouble cleanup to vfs_fruit
...this one...

:)

-slow

-- 
Ralph Boehme, Samba Team       https://samba.org/
Samba Developer, SerNet GmbH   https://sernet.de/en/samba/
GPG Key Fingerprint:           FAE2 C608 8A24 2520 51C5
                               59E4 AA1E 9B71 2639 9E46
-------------- next part --------------
From 8fa3558bc902f5088d9af8fc590aae60413ce8c1 Mon Sep 17 00:00:00 2001
From: Ralph Boehme <slow at samba.org>
Date: Sun, 21 Oct 2018 19:49:14 +0200
Subject: [PATCH 01/35] s3:selftest: vfs test list one by line

Bug: https://bugzilla.samba.org/show_bug.cgi?id=13646

Signed-off-by: Ralph Boehme <slow at samba.org>
---
 source3/selftest/tests.py | 8 +++++++-
 1 file changed, 7 insertions(+), 1 deletion(-)

diff --git a/source3/selftest/tests.py b/source3/selftest/tests.py
index e03c02c018f..38df45f1fcc 100755
--- a/source3/selftest/tests.py
+++ b/source3/selftest/tests.py
@@ -436,7 +436,13 @@ libsmbclient = ["libsmbclient.version", "libsmbclient.initialize",
                 "libsmbclient.options", "libsmbclient.opendir",
                 "libsmbclient.list_shares", "libsmbclient.readdirplus"]
 
-vfs = ["vfs.fruit", "vfs.acl_xattr", "vfs.fruit_netatalk", "vfs.fruit_file_id", "vfs.fruit_timemachine"]
+vfs = [
+    "vfs.fruit",
+    "vfs.acl_xattr",
+    "vfs.fruit_netatalk",
+    "vfs.fruit_file_id",
+    "vfs.fruit_timemachine",
+]
 
 tests = base + raw + smb2 + rpc + unix + local + rap + nbt + libsmbclient + idmap + vfs
 
-- 
2.17.2


From a008566bee2c3533c6b7f86bbbaa26d024c0bcd5 Mon Sep 17 00:00:00 2001
From: Ralph Boehme <slow at samba.org>
Date: Thu, 23 Aug 2018 12:07:20 +0200
Subject: [PATCH 02/35] vfs_streams_xattr: fix open implementation

Since a long time the modules's open function happily returned success
when opening a non existent stream without O_CREAT.

This change fixes it to return -1 and errno=ENOATTR if

o get_ea_value() returns NT_STATUS_NOT_FOUND (eg mapped from
  getxattr() = -1, errno=ENOATTR) and

o flags doesn't contain O_CREAT

Bug: https://bugzilla.samba.org/show_bug.cgi?id=13646

Signed-off-by: Ralph Boehme <slow at samba.org>
---
 source3/modules/vfs_streams_xattr.c | 64 +++++++++++++++++------------
 1 file changed, 38 insertions(+), 26 deletions(-)

diff --git a/source3/modules/vfs_streams_xattr.c b/source3/modules/vfs_streams_xattr.c
index 30459fec4bf..9f4bc13b034 100644
--- a/source3/modules/vfs_streams_xattr.c
+++ b/source3/modules/vfs_streams_xattr.c
@@ -412,6 +412,7 @@ static int streams_xattr_open(vfs_handle_struct *handle,
 	char *xattr_name = NULL;
 	int pipe_fds[2];
 	int fakefd = -1;
+	bool set_empty_xattr = false;
 	int ret;
 
 	SMB_VFS_HANDLE_GET_DATA(handle, config, struct streams_xattr_config,
@@ -445,39 +446,37 @@ static int streams_xattr_open(vfs_handle_struct *handle,
 		goto fail;
 	}
 
-	/*
-	 * Return a valid fd, but ensure any attempt to use it returns an error
-	 * (EPIPE).
-	 */
-	ret = pipe(pipe_fds);
-	if (ret != 0) {
-		goto fail;
-	}
-
-	close(pipe_fds[1]);
-	pipe_fds[1] = -1;
-	fakefd = pipe_fds[0];
-
 	status = get_ea_value(talloc_tos(), handle->conn, NULL,
 			      smb_fname, xattr_name, &ea);
 
 	DEBUG(10, ("get_ea_value returned %s\n", nt_errstr(status)));
 
-	if (!NT_STATUS_IS_OK(status)
-	    && !NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) {
-		/*
-		 * The base file is not there. This is an error even if we got
-		 * O_CREAT, the higher levels should have created the base
-		 * file for us.
-		 */
-		DEBUG(10, ("streams_xattr_open: base file %s not around, "
-			   "returning ENOENT\n", smb_fname->base_name));
-		errno = ENOENT;
-		goto fail;
+	if (!NT_STATUS_IS_OK(status)) {
+		if (!NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) {
+			/*
+			 * The base file is not there. This is an error even if
+			 * we got O_CREAT, the higher levels should have created
+			 * the base file for us.
+			 */
+			DBG_DEBUG("streams_xattr_open: base file %s not around, "
+				  "returning ENOENT\n", smb_fname->base_name);
+			errno = ENOENT;
+			goto fail;
+		}
+
+		if (!(flags & O_CREAT)) {
+			errno = ENOATTR;
+			goto fail;
+		}
+
+		set_empty_xattr = true;
 	}
 
-	if ((!NT_STATUS_IS_OK(status) && (flags & O_CREAT)) ||
-	    (flags & O_TRUNC)) {
+	if (flags & O_TRUNC) {
+		set_empty_xattr = true;
+	}
+
+	if (set_empty_xattr) {
 		/*
 		 * The attribute does not exist or needs to be truncated
 		 */
@@ -500,6 +499,19 @@ static int streams_xattr_open(vfs_handle_struct *handle,
 		}
 	}
 
+	/*
+	 * Return a valid fd, but ensure any attempt to use it returns an error
+	 * (EPIPE).
+	 */
+	ret = pipe(pipe_fds);
+	if (ret != 0) {
+		goto fail;
+	}
+
+	close(pipe_fds[1]);
+	pipe_fds[1] = -1;
+	fakefd = pipe_fds[0];
+
         sio = VFS_ADD_FSP_EXTENSION(handle, fsp, struct stream_io, NULL);
         if (sio == NULL) {
                 errno = ENOMEM;
-- 
2.17.2


From 9329cf35286eaadbcca1844b129d191d538340ae Mon Sep 17 00:00:00 2001
From: Ralph Boehme <slow at samba.org>
Date: Wed, 17 Oct 2018 10:54:10 +0200
Subject: [PATCH 03/35] vfs_streams_xattr: consistently log dev/ino as hex

open_file_ntcreate() logs dev/ino in hex format if it hits a "dev/ino
mismatch". Logging it consistently as hex helps debugging such mismatch
issues.

Bug: https://bugzilla.samba.org/show_bug.cgi?id=13646

Signed-off-by: Ralph Boehme <slow at samba.org>
---
 source3/modules/vfs_streams_xattr.c | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/source3/modules/vfs_streams_xattr.c b/source3/modules/vfs_streams_xattr.c
index 9f4bc13b034..880457a53c1 100644
--- a/source3/modules/vfs_streams_xattr.c
+++ b/source3/modules/vfs_streams_xattr.c
@@ -52,9 +52,9 @@ static SMB_INO_T stream_inode(const SMB_STRUCT_STAT *sbuf, const char *sname)
 	SMB_INO_T result;
 	char *upper_sname;
 
-	DEBUG(10, ("stream_inode called for %lu/%lu [%s]\n",
-		   (unsigned long)sbuf->st_ex_dev,
-		   (unsigned long)sbuf->st_ex_ino, sname));
+	DBG_DEBUG("stream_inode called for 0x%jx/0x%jx [%s]\n",
+		  (uintmax_t)sbuf->st_ex_dev,
+		  (uintmax_t)sbuf->st_ex_ino, sname);
 
 	upper_sname = talloc_strdup_upper(talloc_tos(), sname);
 	SMB_ASSERT(upper_sname != NULL);
@@ -73,7 +73,7 @@ static SMB_INO_T stream_inode(const SMB_STRUCT_STAT *sbuf, const char *sname)
         /* Hopefully all the variation is in the lower 4 (or 8) bytes! */
 	memcpy(&result, hash, sizeof(result));
 
-	DEBUG(10, ("stream_inode returns %lu\n", (unsigned long)result));
+	DBG_DEBUG("stream_inode returns 0x%jx\n", (uintmax_t)result);
 
 	return result;
 }
-- 
2.17.2


From 49a0e63eba7c9210df400f532a8bb1e311f90cd9 Mon Sep 17 00:00:00 2001
From: Ralph Boehme <slow at samba.org>
Date: Fri, 19 Oct 2018 22:21:10 +0200
Subject: [PATCH 04/35] s4:torture/vfs/fruit: skip a few tests when running
 against a macOS SMB server

These tests are designed to test specific vfs_fruit functionality.

Bug: https://bugzilla.samba.org/show_bug.cgi?id=13646

Signed-off-by: Ralph Boehme <slow at samba.org>
---
 source4/torture/vfs/fruit.c | 15 +++++++++++++++
 1 file changed, 15 insertions(+)

diff --git a/source4/torture/vfs/fruit.c b/source4/torture/vfs/fruit.c
index 58a94dd143c..d3756ec8aaf 100644
--- a/source4/torture/vfs/fruit.c
+++ b/source4/torture/vfs/fruit.c
@@ -2111,6 +2111,11 @@ static bool test_adouble_conversion(struct torture_context *tctx,
 		":com.apple.metadata" "\xef\x80\xa2" "_kMDItemUserTags:$DATA",
 		":foo" "\xef\x80\xa2" "bar:$DATA", /* "foo:bar:$DATA" */
 	};
+	bool is_osx = torture_setting_bool(tctx, "osx", false);
+
+	if (is_osx) {
+		torture_skip(tctx, "Test only works with Samba\n");
+	}
 
 	smb2_deltree(tree, BASEDIR);
 
@@ -2185,6 +2190,11 @@ static bool test_adouble_conversion_wo_xattr(struct torture_context *tctx,
 	union smb_search_data *d;
 	const char *data = "This resource fork intentionally left blank";
 	size_t datalen = strlen(data);
+	bool is_osx = torture_setting_bool(tctx, "osx", false);
+
+	if (is_osx) {
+		torture_skip(tctx, "Test only works with Samba\n");
+	}
 
 	smb2_deltree(tree, BASEDIR);
 
@@ -4742,6 +4752,11 @@ static bool test_nfs_aces(struct torture_context *tctx,
 	struct security_descriptor *psd = NULL;
 	NTSTATUS status;
 	bool ret = true;
+	bool is_osx = torture_setting_bool(tctx, "osx", false);
+
+	if (is_osx) {
+		torture_skip(tctx, "Test only works with Samba\n");
+	}
 
 	ret = enable_aapl(tctx, tree);
 	torture_assert(tctx, ret == true, "enable_aapl failed");
-- 
2.17.2


From c097015954e5ab967006ad88a8e53e7426b3f49e Mon Sep 17 00:00:00 2001
From: Ralph Boehme <slow at samba.org>
Date: Wed, 17 Oct 2018 10:51:45 +0200
Subject: [PATCH 05/35] s4:torture/vfs/fruit: fix a few error checks in "delete
 AFP_AfpInfo by writing all 0"

Bug: https://bugzilla.samba.org/show_bug.cgi?id=13646

Signed-off-by: Ralph Boehme <slow at samba.org>
---
 source4/torture/vfs/fruit.c | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/source4/torture/vfs/fruit.c b/source4/torture/vfs/fruit.c
index d3756ec8aaf..702c3d2edcd 100644
--- a/source4/torture/vfs/fruit.c
+++ b/source4/torture/vfs/fruit.c
@@ -3675,8 +3675,8 @@ static bool test_afpinfo_all0(struct torture_context *tctx,
 	create.in.fname = fname;
 
 	status = smb2_create(tree, mem_ctx, &create);
-	torture_assert_goto(tctx, ret == true, ret, done,
-			    "smb2_create failed\n");
+	torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
+					"smb2_create failed\n");
 	baseh = create.out.file.handle;
 
 	ZERO_STRUCT(create);
@@ -3686,8 +3686,8 @@ static bool test_afpinfo_all0(struct torture_context *tctx,
 	create.in.fname = sname;
 
 	status = smb2_create(tree, mem_ctx, &create);
-	torture_assert_goto(tctx, ret == true, ret, done,
-			    "smb2_create failed\n");
+	torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
+					"smb2_create failed\n");
 	h1 = create.out.file.handle;
 
 	status = smb2_util_write(tree, h1, infobuf, 0, AFP_INFO_SIZE);
-- 
2.17.2


From e8b5bb2b6859bf6fa2307c99510f46a5749a262d Mon Sep 17 00:00:00 2001
From: Ralph Boehme <slow at samba.org>
Date: Thu, 11 Oct 2018 17:13:52 +0200
Subject: [PATCH 06/35] s4:torture/vfs/fruit: set share_access to
 NTCREATEX_SHARE_ACCESS_MASK in check_stream_list

Avoid sharing conflicts with other opens on the basefile.

Bug: https://bugzilla.samba.org/show_bug.cgi?id=13646

Signed-off-by: Ralph Boehme <slow at samba.org>
---
 source4/torture/vfs/fruit.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/source4/torture/vfs/fruit.c b/source4/torture/vfs/fruit.c
index 702c3d2edcd..7ee43676d0b 100644
--- a/source4/torture/vfs/fruit.c
+++ b/source4/torture/vfs/fruit.c
@@ -3053,6 +3053,7 @@ static bool check_stream_list(struct smb2_tree *tree,
 	create.in.desired_access = SEC_FILE_ALL;
 	create.in.create_options = is_dir ? NTCREATEX_OPTIONS_DIRECTORY : 0;
 	create.in.file_attributes = is_dir ? FILE_ATTRIBUTE_DIRECTORY : FILE_ATTRIBUTE_NORMAL;
+	create.in.share_access = NTCREATEX_SHARE_ACCESS_MASK;
 	status = smb2_create(tree, tmp_ctx, &create);
 	torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "smb2_create");
 	h = create.out.file.handle;
-- 
2.17.2


From 3db3ef5395482517db37676179093a95f1934710 Mon Sep 17 00:00:00 2001
From: Ralph Boehme <slow at samba.org>
Date: Mon, 15 Oct 2018 15:31:21 +0200
Subject: [PATCH 07/35] s4:torture/vfs/fruit: update test "SMB2/CREATE context
 AAPL" to work against macOS

Bug: https://bugzilla.samba.org/show_bug.cgi?id=13646

Signed-off-by: Ralph Boehme <slow at samba.org>
---
 source4/torture/vfs/fruit.c | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/source4/torture/vfs/fruit.c b/source4/torture/vfs/fruit.c
index 7ee43676d0b..2509656a13a 100644
--- a/source4/torture/vfs/fruit.c
+++ b/source4/torture/vfs/fruit.c
@@ -2306,6 +2306,7 @@ static bool test_aapl(struct torture_context *tctx,
 	uint32_t aapl_reply_bitmap;
 	uint32_t aapl_server_caps;
 	uint32_t aapl_vol_caps;
+	uint32_t expected_vol_caps = 0;
 	char *model;
 	struct smb2_find f;
 	unsigned int count;
@@ -2424,8 +2425,11 @@ static bool test_aapl(struct torture_context *tctx,
 		goto done;
 	}
 
+	if (is_osx_server) {
+		expected_vol_caps = 5;
+	}
 	aapl_vol_caps = BVAL(aapl->data.data, 24);
-	if (aapl_vol_caps != 0) {
+	if (aapl_vol_caps != expected_vol_caps) {
 		/* this will fail on a case insensitive fs ... */
 		torture_result(tctx, TORTURE_FAIL,
 				"(%s) unexpected vol_caps: %d",
-- 
2.17.2


From f46630c1d2206f52df2c3705b395772624cac231 Mon Sep 17 00:00:00 2001
From: Ralph Boehme <slow at samba.org>
Date: Mon, 15 Oct 2018 15:39:12 +0200
Subject: [PATCH 08/35] s4:torture/vfs/fruit: update test "stream names" to
 work with macOS

o create the basefile before trying to create a stream on it, otherwise
  this fails on macOS

o write something to the stream, otherwise the stream is not listed as
  macOS hides 0-byte sized streams

Bug: https://bugzilla.samba.org/show_bug.cgi?id=13646

Signed-off-by: Ralph Boehme <slow at samba.org>
---
 source4/torture/vfs/fruit.c | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/source4/torture/vfs/fruit.c b/source4/torture/vfs/fruit.c
index 2509656a13a..298b0118dbc 100644
--- a/source4/torture/vfs/fruit.c
+++ b/source4/torture/vfs/fruit.c
@@ -3132,6 +3132,9 @@ static bool test_stream_names(struct torture_context *tctx,
 	CHECK_STATUS(status, NT_STATUS_OK);
 	smb2_util_close(tree, h);
 
+	ret = torture_setup_file(mem_ctx, tree, fname, false);
+	torture_assert_goto(tctx, ret, ret, done, "torture_setup_file");
+
 	torture_comment(tctx, "(%s) testing stream names\n", __location__);
 	ZERO_STRUCT(create);
 	create.in.desired_access = SEC_FILE_WRITE_DATA;
@@ -3146,6 +3149,11 @@ static bool test_stream_names(struct torture_context *tctx,
 
 	status = smb2_create(tree, mem_ctx, &create);
 	CHECK_STATUS(status, NT_STATUS_OK);
+
+	status = smb2_util_write(tree, create.out.file.handle, "foo", 0, 3);
+	torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
+					"smb2_util_write failed\n");
+
 	smb2_util_close(tree, create.out.file.handle);
 
 	ret = check_stream_list(tree, tctx, fname, 2, streams, false);
-- 
2.17.2


From b888d60a911c1f764e484988f6ab3fa72572a6e5 Mon Sep 17 00:00:00 2001
From: Ralph Boehme <slow at samba.org>
Date: Mon, 15 Oct 2018 16:03:03 +0200
Subject: [PATCH 09/35] s4:torture/vfs/fruit: update test "rename_dir_openfile"
 to work against macOS

The test opens a directory twice where the first open didn't allow
sharing. No idea why this works against Samba, against macOS the second
open got a sharing violation. Obvious fix: add proper sharing.

Bug: https://bugzilla.samba.org/show_bug.cgi?id=13646

Signed-off-by: Ralph Boehme <slow at samba.org>
---
 source4/torture/vfs/fruit.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/source4/torture/vfs/fruit.c b/source4/torture/vfs/fruit.c
index 298b0118dbc..4f69194300e 100644
--- a/source4/torture/vfs/fruit.c
+++ b/source4/torture/vfs/fruit.c
@@ -3190,7 +3190,7 @@ static bool test_rename_dir_openfile(struct torture_context *torture,
 	io.smb2.in.desired_access = 0x0017019f;
 	io.smb2.in.create_options = NTCREATEX_OPTIONS_DIRECTORY;
 	io.smb2.in.file_attributes = FILE_ATTRIBUTE_DIRECTORY;
-	io.smb2.in.share_access = 0;
+	io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_MASK;
 	io.smb2.in.alloc_size = 0;
 	io.smb2.in.create_disposition = NTCREATEX_DISP_CREATE;
 	io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS;
@@ -3252,7 +3252,7 @@ static bool test_rename_dir_openfile(struct torture_context *torture,
 	io.generic.level = RAW_OPEN_SMB2;
 	io.smb2.in.desired_access = 0x0017019f;
 	io.smb2.in.file_attributes = FILE_ATTRIBUTE_DIRECTORY;
-	io.smb2.in.share_access = 0;
+	io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_MASK;
 	io.smb2.in.alloc_size = 0;
 	io.smb2.in.create_disposition = NTCREATEX_DISP_OPEN;
 	io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS;
-- 
2.17.2


From a29c47337ff95006f26547d366dad487f9d50599 Mon Sep 17 00:00:00 2001
From: Ralph Boehme <slow at samba.org>
Date: Mon, 15 Oct 2018 16:24:19 +0200
Subject: [PATCH 10/35] s4:torture/vfs/fruit: update test "read open rsrc after
 rename" to work with macOS

macOS SMB server seems to return NT_STATUS_SHARING_VIOLATION in this
case while Windows 2016 returns NT_STATUS_ACCESS_DENIED.

Lets stick with the Windows error code for now in the Samba fileserver,
but let the test pass against macOS.

Bug: https://bugzilla.samba.org/show_bug.cgi?id=13646

Signed-off-by: Ralph Boehme <slow at samba.org>
---
 source4/torture/vfs/fruit.c | 17 +++++++++++++++--
 1 file changed, 15 insertions(+), 2 deletions(-)

diff --git a/source4/torture/vfs/fruit.c b/source4/torture/vfs/fruit.c
index 4f69194300e..034f6374bdf 100644
--- a/source4/torture/vfs/fruit.c
+++ b/source4/torture/vfs/fruit.c
@@ -4182,6 +4182,8 @@ static bool test_rename_and_read_rsrc(struct torture_context *tctx,
 	const char *fname_renamed = "test_rename_openfile_renamed";
 	const char *data = "1234567890";
 	union smb_setfileinfo sinfo;
+	bool server_is_macos = torture_setting_bool(tctx, "osx", false);
+	NTSTATUS expected_status;
 
 	ret = enable_aapl(tctx, tree);
 	torture_assert_goto(tctx, ret == true, ret, done, "enable_aapl failed");
@@ -4232,14 +4234,25 @@ static bool test_rename_and_read_rsrc(struct torture_context *tctx,
 	sinfo.rename_information.in.root_fid = 0;
 	sinfo.rename_information.in.new_name = fname_renamed;
 
+	if (server_is_macos) {
+		expected_status = NT_STATUS_SHARING_VIOLATION;
+	} else {
+		expected_status = NT_STATUS_ACCESS_DENIED;
+	}
+
 	status = smb2_setinfo_file(tree, &sinfo);
 	torture_assert_ntstatus_equal_goto(
-		tctx, status, NT_STATUS_ACCESS_DENIED, ret, done,
+		tctx, status, expected_status, ret, done,
 		"smb2_setinfo_file failed");
 
-	smb2_util_close(tree, h1);
 	smb2_util_close(tree, h2);
 
+	status = smb2_util_write(tree, h1, "foo", 0, 3);
+	torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
+					"write failed\n");
+
+	smb2_util_close(tree, h1);
+
 done:
 	smb2_util_unlink(tree, fname);
 	smb2_util_unlink(tree, fname_renamed);
-- 
2.17.2


From 7fdcd222782be00d9c1e184bdfe19cdf15ad73b6 Mon Sep 17 00:00:00 2001
From: Ralph Boehme <slow at samba.org>
Date: Tue, 9 Oct 2018 18:48:08 +0200
Subject: [PATCH 11/35] s4:torture/vfs/fruit: expand existing test "setinfo
 delete-on-close AFP_AfpInfo" a little bit

Add a check that verifies a create on a stream gets
NT_STATUS_DELETE_PENDING after delete-on-close has been set.

Bug: https://bugzilla.samba.org/show_bug.cgi?id=13646

Signed-off-by: Ralph Boehme <slow at samba.org>
---
 source4/torture/vfs/fruit.c | 17 +++++++++++++++++
 1 file changed, 17 insertions(+)

diff --git a/source4/torture/vfs/fruit.c b/source4/torture/vfs/fruit.c
index 034f6374bdf..b65286ad9b7 100644
--- a/source4/torture/vfs/fruit.c
+++ b/source4/torture/vfs/fruit.c
@@ -3453,6 +3453,10 @@ static bool test_setinfo_delete_on_close(struct torture_context *tctx,
 	const char *sname = BASEDIR "\\file" AFPINFO_STREAM_NAME;
 	const char *type_creator = "SMB,OLE!";
 	AfpInfo *info = NULL;
+	const char *streams[] = {
+		AFPINFO_STREAM,
+		"::$DATA"
+	};
 	const char *streams_basic[] = {
 		"::$DATA"
 	};
@@ -3494,6 +3498,19 @@ static bool test_setinfo_delete_on_close(struct torture_context *tctx,
 	status = smb2_setinfo_file(tree, &sfinfo);
 	torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "set delete-on-close failed");
 
+	ret = check_stream_list(tree, tctx, fname, 2, streams, false);
+	torture_assert_goto(tctx, ret == true, ret, done, "Bad streams");
+
+	ZERO_STRUCT(create);
+	create.in.create_disposition = NTCREATEX_DISP_OPEN;
+	create.in.desired_access = SEC_FILE_ALL;
+	create.in.fname = sname;
+	create.in.file_attributes = FILE_ATTRIBUTE_NORMAL;
+	create.in.impersonation_level = NTCREATEX_IMPERSONATION_IMPERSONATION;
+	status = smb2_create(tree, mem_ctx, &create);
+	torture_assert_ntstatus_equal_goto(tctx, status, NT_STATUS_DELETE_PENDING,
+					   ret, done, "Got unexpected AFP_AfpInfo stream");
+
 	smb2_util_close(tree, h1);
 
 	ret = check_stream_list(tree, tctx, fname, 1, streams_basic, false);
-- 
2.17.2


From 80e91ed1aba51bf09d090eab29079055498d39dc Mon Sep 17 00:00:00 2001
From: Ralph Boehme <slow at samba.org>
Date: Wed, 10 Oct 2018 12:47:07 +0200
Subject: [PATCH 12/35] s4:torture/vfs/fruit: expand existing vfs_test "null
 afpinfo"

This adds a check that a read on a seperate handle also sees the
previously created AFP_AfpInfo stream.

Bug: https://bugzilla.samba.org/show_bug.cgi?id=13646

Signed-off-by: Ralph Boehme <slow at samba.org>
---
 source4/torture/vfs/fruit.c | 16 ++++++++++++++++
 1 file changed, 16 insertions(+)

diff --git a/source4/torture/vfs/fruit.c b/source4/torture/vfs/fruit.c
index b65286ad9b7..4e1a91ba989 100644
--- a/source4/torture/vfs/fruit.c
+++ b/source4/torture/vfs/fruit.c
@@ -4094,6 +4094,8 @@ static bool test_null_afpinfo(struct torture_context *tctx,
 	AfpInfo *afpinfo = NULL;
 	char *afpinfo_buf = NULL;
 	const char *type_creator = "SMB,OLE!";
+	struct smb2_handle handle2;
+	struct smb2_read r;
 
 	torture_comment(tctx, "Checking create of AfpInfo stream\n");
 
@@ -4132,6 +4134,20 @@ static bool test_null_afpinfo(struct torture_context *tctx,
 	status = smb2_read_recv(req[1], tree, &read);
 	torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "smb2_read_recv failed");
 
+	status = torture_smb2_testfile_access(tree, sname, &handle2,
+					      SEC_FILE_READ_DATA);
+	torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
+					"torture_smb2_testfile failed\n");
+	r = (struct smb2_read) {
+		.in.file.handle = handle2,
+		.in.length      = AFP_INFO_SIZE,
+	};
+
+	status = smb2_read(tree, tree, &r);
+	torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
+					"torture_smb2_testfile failed\n");
+	smb2_util_close(tree, handle2);
+
 	afpinfo = torture_afpinfo_new(mem_ctx);
 	torture_assert_goto(tctx, afpinfo != NULL, ret, done, "torture_afpinfo_new failed");
 
-- 
2.17.2


From bb3969b69fe5caa595f0bd73899218a066da56d0 Mon Sep 17 00:00:00 2001
From: Ralph Boehme <slow at samba.org>
Date: Mon, 15 Oct 2018 15:17:08 +0200
Subject: [PATCH 13/35] s4:torture/vfs/fruit: update test "creating rsrc with
 read-only access" for newer macOS versions

While this operation failed against older macOS versions, it passes
against versions 10.12 and newer. Update the test accordingly, a
subsequent commit will then update our implementation.

Bug: https://bugzilla.samba.org/show_bug.cgi?id=13646

Signed-off-by: Ralph Boehme <slow at samba.org>
---
 selftest/knownfail.d/samba3.vfs.fruit |  3 +++
 source4/torture/vfs/fruit.c           | 28 +--------------------------
 2 files changed, 4 insertions(+), 27 deletions(-)

diff --git a/selftest/knownfail.d/samba3.vfs.fruit b/selftest/knownfail.d/samba3.vfs.fruit
index d401b2d9a88..31b91a0a45b 100644
--- a/selftest/knownfail.d/samba3.vfs.fruit
+++ b/selftest/knownfail.d/samba3.vfs.fruit
@@ -1,3 +1,6 @@
 ^samba3.vfs.fruit streams_depot.OS X AppleDouble file conversion\(nt4_dc\)
 ^samba3.vfs.fruit streams_depot.OS X AppleDouble file conversion without embedded xattr\(nt4_dc\)
 ^samba3.vfs.fruit_netatalk.locking conflict\(nt4_dc\)
+^samba3.vfs.fruit metadata_netatalk.creating rsrc with read-only access\(nt4_dc\)
+^samba3.vfs.fruit metadata_stream.creating rsrc with read-only access\(nt4_dc\)
+^samba3.vfs.fruit streams_depot.creating rsrc with read-only access\(nt4_dc\)
diff --git a/source4/torture/vfs/fruit.c b/source4/torture/vfs/fruit.c
index 4e1a91ba989..0406ad4e8dd 100644
--- a/source4/torture/vfs/fruit.c
+++ b/source4/torture/vfs/fruit.c
@@ -2045,35 +2045,9 @@ static bool test_rfork_create_ro(struct torture_context *tctx,
 	}
 
 	torture_comment(tctx, "(%s) Try opening read-only with "
-			"open_if create disposition, should return ENOENT\n",
+			"open_if create disposition, should work\n",
 			__location__);
 
-	ZERO_STRUCT(create);
-	create.in.fname = rfork;
-	create.in.create_disposition = NTCREATEX_DISP_OPEN_IF;
-	create.in.desired_access = SEC_FILE_READ_DATA | SEC_STD_READ_CONTROL;
-	create.in.file_attributes = FILE_ATTRIBUTE_NORMAL;
-	create.in.share_access = FILE_SHARE_READ | FILE_SHARE_DELETE;
-	status = smb2_create(tree, mem_ctx, &(create));
-	torture_assert_ntstatus_equal_goto(tctx, status,
-					NT_STATUS_OBJECT_NAME_NOT_FOUND,
-					ret, done, "smb2_create failed\n");
-
-	torture_comment(tctx, "(%s) Now write something to the "
-			"rsrc stream, then the same open should succeed\n",
-			__location__);
-
-	ret = write_stream(tree, __location__, tctx, mem_ctx,
-			   fname, AFPRESOURCE_STREAM_NAME,
-			   0, 3, "foo");
-	torture_assert_goto(tctx, ret == true, ret, done,
-			"write_stream failed\n");
-
-	ret = check_stream(tree, __location__, tctx, mem_ctx,
-			   fname, AFPRESOURCE_STREAM,
-			   0, 3, 0, 3, "foo");
-	torture_assert_goto(tctx, ret == true, ret, done, "check_stream");
-
 	ZERO_STRUCT(create);
 	create.in.fname = rfork;
 	create.in.create_disposition = NTCREATEX_DISP_OPEN_IF;
-- 
2.17.2


From 3d8d6ea1e27892ad19d15eef3240768bae12300d Mon Sep 17 00:00:00 2001
From: Ralph Boehme <slow at samba.org>
Date: Mon, 22 Oct 2018 12:32:09 +0200
Subject: [PATCH 14/35] vfs_fruit: update handling of read-only creation of
 resource fork

macOS SMB server versions supports this since 10.12, so we adapt our
behaviour.

Bug: https://bugzilla.samba.org/show_bug.cgi?id=13646

Signed-off-by: Ralph Boehme <slow at samba.org>
---
 selftest/knownfail.d/samba3.vfs.fruit |  3 ---
 source3/modules/vfs_fruit.c           | 23 +++--------------------
 2 files changed, 3 insertions(+), 23 deletions(-)

diff --git a/selftest/knownfail.d/samba3.vfs.fruit b/selftest/knownfail.d/samba3.vfs.fruit
index 31b91a0a45b..d401b2d9a88 100644
--- a/selftest/knownfail.d/samba3.vfs.fruit
+++ b/selftest/knownfail.d/samba3.vfs.fruit
@@ -1,6 +1,3 @@
 ^samba3.vfs.fruit streams_depot.OS X AppleDouble file conversion\(nt4_dc\)
 ^samba3.vfs.fruit streams_depot.OS X AppleDouble file conversion without embedded xattr\(nt4_dc\)
 ^samba3.vfs.fruit_netatalk.locking conflict\(nt4_dc\)
-^samba3.vfs.fruit metadata_netatalk.creating rsrc with read-only access\(nt4_dc\)
-^samba3.vfs.fruit metadata_stream.creating rsrc with read-only access\(nt4_dc\)
-^samba3.vfs.fruit streams_depot.creating rsrc with read-only access\(nt4_dc\)
diff --git a/source3/modules/vfs_fruit.c b/source3/modules/vfs_fruit.c
index 30fab85ee98..f46edc8e077 100644
--- a/source3/modules/vfs_fruit.c
+++ b/source3/modules/vfs_fruit.c
@@ -3418,12 +3418,9 @@ static int fruit_open_rsrc_adouble(vfs_handle_struct *handle,
 		goto exit;
 	}
 
-	/* Sanitize flags */
-	if (flags & O_WRONLY) {
-		/* We always need read access for the metadata header too */
-		flags &= ~O_WRONLY;
-		flags |= O_RDWR;
-	}
+	/* We always need read/write access for the metadata header too */
+	flags &= ~(O_RDONLY | O_WRONLY);
+	flags |= O_RDWR;
 
 	hostfd = SMB_VFS_NEXT_OPEN(handle, smb_fname_base, fsp,
 				   flags, mode);
@@ -3510,20 +3507,6 @@ static int fruit_open_rsrc(vfs_handle_struct *handle,
 	SMB_VFS_HANDLE_GET_DATA(handle, config,
 				struct fruit_config_data, return -1);
 
-	if (((flags & O_ACCMODE) == O_RDONLY)
-	    && (flags & O_CREAT)
-	    && !VALID_STAT(fsp->fsp_name->st))
-	{
-		/*
-		 * This means the stream doesn't exist. macOS SMB server fails
-		 * this with NT_STATUS_OBJECT_NAME_NOT_FOUND, so must we. Cf bug
-		 * 12565 and the test for this combination in
-		 * test_rfork_create().
-		 */
-		errno = ENOENT;
-		return -1;
-	}
-
 	switch (config->rsrc) {
 	case FRUIT_RSRC_STREAM:
 		fd = SMB_VFS_NEXT_OPEN(handle, smb_fname, fsp, flags, mode);
-- 
2.17.2


From 73c9f15711496d821ef62b55d412527f1e11a041 Mon Sep 17 00:00:00 2001
From: Ralph Boehme <slow at samba.org>
Date: Mon, 22 Oct 2018 12:43:16 +0200
Subject: [PATCH 15/35] s4:torture/vfs/fruit: expand test "setinfo eof stream"

o Adds checks verifying that after setting eof to 0 on a stream, a
  subsequent open gets ENOENT, before and after closing the handle that
  had been used to set eof to 0.

o Verify that a write to a handle succeeds after that handle has been
  used to set eof to 0 on a stream.

Bug: https://bugzilla.samba.org/show_bug.cgi?id=13646

Signed-off-by: Ralph Boehme <slow at samba.org>
---
 selftest/knownfail.d/samba3.vfs.fruit |  3 ++
 source4/torture/vfs/fruit.c           | 61 +++++++++++++++++++++++++++
 2 files changed, 64 insertions(+)

diff --git a/selftest/knownfail.d/samba3.vfs.fruit b/selftest/knownfail.d/samba3.vfs.fruit
index d401b2d9a88..7a110c556f9 100644
--- a/selftest/knownfail.d/samba3.vfs.fruit
+++ b/selftest/knownfail.d/samba3.vfs.fruit
@@ -1,3 +1,6 @@
 ^samba3.vfs.fruit streams_depot.OS X AppleDouble file conversion\(nt4_dc\)
 ^samba3.vfs.fruit streams_depot.OS X AppleDouble file conversion without embedded xattr\(nt4_dc\)
 ^samba3.vfs.fruit_netatalk.locking conflict\(nt4_dc\)
+^samba3.vfs.fruit metadata_netatalk.setinfo eof stream\(nt4_dc\)
+^samba3.vfs.fruit metadata_stream.setinfo eof stream\(nt4_dc\)
+^samba3.vfs.fruit streams_depot.setinfo eof stream\(nt4_dc\)
diff --git a/source4/torture/vfs/fruit.c b/source4/torture/vfs/fruit.c
index 0406ad4e8dd..5161588d873 100644
--- a/source4/torture/vfs/fruit.c
+++ b/source4/torture/vfs/fruit.c
@@ -4984,8 +4984,32 @@ static bool test_setinfo_stream_eof(struct torture_context *tctx,
 	torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
 					"set eof 0 failed\n");
 
+	ZERO_STRUCT(create);
+	create.in.desired_access = SEC_FILE_READ_ATTRIBUTE;
+	create.in.share_access = NTCREATEX_SHARE_ACCESS_MASK;
+	create.in.file_attributes = FILE_ATTRIBUTE_NORMAL;
+	create.in.create_disposition = NTCREATEX_DISP_OPEN;
+	create.in.fname = sname;
+
+	status = smb2_create(tree, tctx, &create);
+	torture_assert_ntstatus_equal_goto(
+		tctx, status, NT_STATUS_OBJECT_NAME_NOT_FOUND, ret, done,
+		"Unexpected status\n");
+
 	smb2_util_close(tree, h1);
 
+	ZERO_STRUCT(create);
+	create.in.desired_access = SEC_FILE_READ_ATTRIBUTE;
+	create.in.share_access = NTCREATEX_SHARE_ACCESS_MASK;
+	create.in.file_attributes = FILE_ATTRIBUTE_NORMAL;
+	create.in.create_disposition = NTCREATEX_DISP_OPEN;
+	create.in.fname = sname;
+
+	status = smb2_create(tree, tctx, &create);
+	torture_assert_ntstatus_equal_goto(
+		tctx, status, NT_STATUS_OBJECT_NAME_NOT_FOUND, ret, done,
+		"Unexpected status\n");
+
 	status = torture_smb2_testfile_access(tree, sname, &h1,
 					      SEC_FILE_WRITE_DATA);
 	torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
@@ -5064,6 +5088,18 @@ static bool test_setinfo_stream_eof(struct torture_context *tctx,
 	torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
 					"set eof 0 failed\n");
 
+	ZERO_STRUCT(create);
+	create.in.desired_access = SEC_FILE_READ_ATTRIBUTE;
+	create.in.share_access = NTCREATEX_SHARE_ACCESS_MASK;
+	create.in.file_attributes = FILE_ATTRIBUTE_NORMAL;
+	create.in.create_disposition = NTCREATEX_DISP_OPEN;
+	create.in.fname = sname;
+
+	status = smb2_create(tree, tctx, &create);
+	torture_assert_ntstatus_equal_goto(
+		tctx, status, NT_STATUS_OBJECT_NAME_NOT_FOUND, ret, done,
+		"Unexpected status\n");
+
 	smb2_util_close(tree, h1);
 
 	ZERO_STRUCT(create);
@@ -5123,6 +5159,31 @@ static bool test_setinfo_stream_eof(struct torture_context *tctx,
 	torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
 					"torture_smb2_testfile failed\n");
 	smb2_util_close(tree, h1);
+
+	torture_comment(tctx, "Writing to stream after setting EOF to 0\n");
+	status = torture_smb2_testfile_access(tree, sname, &h1,
+					      SEC_FILE_WRITE_DATA);
+	torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
+					"torture_smb2_testfile failed\n");
+
+	status = smb2_util_write(tree, h1, "1234567890", 0, 10);
+	torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
+					"smb2_util_write failed\n");
+
+	ZERO_STRUCT(sfinfo);
+	sfinfo.generic.in.file.handle = h1;
+	sfinfo.generic.level = RAW_SFILEINFO_END_OF_FILE_INFORMATION;
+	sfinfo.position_information.in.position = 0;
+	status = smb2_setinfo_file(tree, &sfinfo);
+	torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
+					"set eof 0 failed\n");
+
+	status = smb2_util_write(tree, h1, "1234567890", 0, 10);
+	torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
+					"smb2_util_write failed\n");
+
+	smb2_util_close(tree, h1);
+
 done:
 	smb2_util_unlink(tree, fname);
 	smb2_util_rmdir(tree, BASEDIR);
-- 
2.17.2


From d9a2517d2297b5831dbeb4cc9381ef40b6aa43ec Mon Sep 17 00:00:00 2001
From: Ralph Boehme <slow at samba.org>
Date: Sat, 20 Oct 2018 14:52:23 +0200
Subject: [PATCH 16/35] s4:torture/vfs/fruit: write some data to a just created
 teststream

Doesn't currently make a difference, but this prepares for a later
change in vfs_fruit that will filter out empty streams (which is the
macOS behaviour).

Bug: https://bugzilla.samba.org/show_bug.cgi?id=13646

Signed-off-by: Ralph Boehme <slow at samba.org>
---
 source4/torture/vfs/fruit.c | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/source4/torture/vfs/fruit.c b/source4/torture/vfs/fruit.c
index 5161588d873..53b9246804f 100644
--- a/source4/torture/vfs/fruit.c
+++ b/source4/torture/vfs/fruit.c
@@ -5285,6 +5285,11 @@ static bool test_stream_names_local(struct torture_context *tctx,
 
 	status = smb2_create(tree, mem_ctx, &create);
 	CHECK_STATUS(status, NT_STATUS_OK);
+
+	status = smb2_util_write(tree, create.out.file.handle, "foo", 0, 3);
+	torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
+					"smb2_util_write failed\n");
+
 	smb2_util_close(tree, create.out.file.handle);
 
 	ret = torture_setup_local_xattr(tctx, "localdir", BASEDIR "/stream_names.txt",
-- 
2.17.2


From a9417a65b236353ba953ed7052ae9c0b77466d83 Mon Sep 17 00:00:00 2001
From: Ralph Boehme <slow at samba.org>
Date: Sat, 20 Oct 2018 14:54:48 +0200
Subject: [PATCH 17/35] vfs_fruit: don't unlink 0-byte size truncated streams

This caused all sort of havoc with subsequent SMB request that acted on
the handle of the then deleted backend storage (file or blob, depending
on the used streams module).

Bug: https://bugzilla.samba.org/show_bug.cgi?id=13646

Signed-off-by: Ralph Boehme <slow at samba.org>
---
 source3/modules/vfs_fruit.c | 7 -------
 1 file changed, 7 deletions(-)

diff --git a/source3/modules/vfs_fruit.c b/source3/modules/vfs_fruit.c
index f46edc8e077..c32fb4517f7 100644
--- a/source3/modules/vfs_fruit.c
+++ b/source3/modules/vfs_fruit.c
@@ -5659,13 +5659,6 @@ static int fruit_ftruncate(struct vfs_handle_struct *handle,
 		  (intmax_t)offset);
 
 	if (fio == NULL) {
-		if (offset == 0 &&
-		    global_fruit_config.nego_aapl &&
-		    is_ntfs_stream_smb_fname(fsp->fsp_name) &&
-		    !is_ntfs_default_stream_smb_fname(fsp->fsp_name))
-		{
-			return SMB_VFS_NEXT_UNLINK(handle, fsp->fsp_name);
-		}
 		return SMB_VFS_NEXT_FTRUNCATE(handle, fsp, offset);
 	}
 
-- 
2.17.2


From d083a7c1db999a4e7ef70dc2be6c2a9524a4662c Mon Sep 17 00:00:00 2001
From: Ralph Boehme <slow at samba.org>
Date: Mon, 22 Oct 2018 14:01:34 +0200
Subject: [PATCH 18/35] s4:torture/vfs/fruit: enable AAPL extensions in a bunch
 of tests

These tests check for macOS SMB server specific behaviour. They work
currently against Samba without enabling AAPL because in vfs_fruit we're
currently don't check whether AAPL has been negotiated in one place. A
subsequent commit will change that and this commit prepares for that
change.

Bug: https://bugzilla.samba.org/show_bug.cgi?id=13646

Signed-off-by: Ralph Boehme <slow at samba.org>
---
 source4/torture/vfs/fruit.c | 12 ++++++++++++
 1 file changed, 12 insertions(+)

diff --git a/source4/torture/vfs/fruit.c b/source4/torture/vfs/fruit.c
index 53b9246804f..11c287aeb4c 100644
--- a/source4/torture/vfs/fruit.c
+++ b/source4/torture/vfs/fruit.c
@@ -1810,6 +1810,9 @@ static bool test_rfork_truncate(struct torture_context *tctx,
 	struct smb2_handle fh1, fh2, fh3;
 	union smb_setfileinfo sinfo;
 
+	ret = enable_aapl(tctx, tree);
+	torture_assert_goto(tctx, ret == true, ret, done, "enable_aapl failed");
+
 	smb2_util_unlink(tree, fname);
 
 	status = torture_smb2_testdir(tree, BASEDIR, &testdirh);
@@ -1928,6 +1931,9 @@ static bool test_rfork_create(struct torture_context *tctx,
 	};
 	union smb_fileinfo finfo;
 
+	ret = enable_aapl(tctx, tree);
+	torture_assert_goto(tctx, ret == true, ret, done, "enable_aapl failed");
+
 	smb2_util_unlink(tree, fname);
 
 	status = torture_smb2_testdir(tree, BASEDIR, &testdirh);
@@ -3938,6 +3944,9 @@ static bool test_setinfo_eof_resource(struct torture_context *tctx,
 
 	torture_assert_goto(tctx, mem_ctx != NULL, ret, done, "talloc_new");
 
+	ret = enable_aapl(tctx, tree);
+	torture_assert_goto(tctx, ret == true, ret, done, "enable_aapl failed");
+
 	torture_comment(tctx, "Set AFP_AfpResource EOF to 1 and 0\n");
 
 	smb2_deltree(tree, BASEDIR);
@@ -4904,6 +4913,9 @@ static bool test_setinfo_stream_eof(struct torture_context *tctx,
 	torture_assert_goto(tctx, mem_ctx != NULL, ret, done,
 			    "talloc_new failed\n");
 
+	ret = enable_aapl(tctx, tree);
+	torture_assert(tctx, ret == true, "enable_aapl failed");
+
 	torture_comment(tctx, "Test setting EOF on a stream\n");
 
 	smb2_deltree(tree, BASEDIR);
-- 
2.17.2


From ba6668746a6a792e0d8afce5954954505c65d276 Mon Sep 17 00:00:00 2001
From: Ralph Boehme <slow at samba.org>
Date: Sat, 20 Oct 2018 15:28:06 +0200
Subject: [PATCH 19/35] vfs_fruit: use check on global_fruit_config.nego_aapl
 for macOS specific behaviour

Ensure any non MS compliant protocol behaviour targetted at supporting
macOS clients are only effective if the client negotiated AAPL.

Currently this only guards the resource fork which only macOS client are
going to use, but subsequent commits add more this at this place.

Bug: https://bugzilla.samba.org/show_bug.cgi?id=13646

Signed-off-by: Ralph Boehme <slow at samba.org>
---
 source3/modules/vfs_fruit.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/source3/modules/vfs_fruit.c b/source3/modules/vfs_fruit.c
index c32fb4517f7..ad7a364b699 100644
--- a/source3/modules/vfs_fruit.c
+++ b/source3/modules/vfs_fruit.c
@@ -5735,7 +5735,8 @@ static NTSTATUS fruit_create_file(vfs_handle_struct *handle,
 	 *
 	 * Cf the vfs_fruit torture tests in test_rfork_create().
 	 */
-	if (is_afpresource_stream(fsp->fsp_name) &&
+	if (global_fruit_config.nego_aapl &&
+	    is_afpresource_stream(fsp->fsp_name) &&
 	    create_disposition == FILE_OPEN)
 	{
 		if (fsp->fsp_name->st.st_ex_size == 0) {
-- 
2.17.2


From cd667e7abe0fd962f8eefab1fac6c7334e60c444 Mon Sep 17 00:00:00 2001
From: Ralph Boehme <slow at samba.org>
Date: Sat, 20 Oct 2018 14:53:50 +0200
Subject: [PATCH 20/35] vfs_fruit: filter empty streams

First step in achieving macOS compliant behaviour wrt to empty streams:
- hide empty streams in streaminfo
- prevent opens of empty streams

This means that we may carry 0-byte sized streams in our streams
backend, but this shouldn't really hurt.

The previous attempt of deleting the streams when an SMB setinfo eof to
0 request came in, turned out be a road into desaster.

We could set delete-on-close on the stream, but that means we'd have to
check for it for every write on a stream and checking the
delete-on-close bits requires fetching the locking.tdb record, so this
is expensive and I'd like to avoid that overhead.

Bug: https://bugzilla.samba.org/show_bug.cgi?id=13646

Signed-off-by: Ralph Boehme <slow at samba.org>
---
 selftest/knownfail.d/samba3.vfs.fruit |  3 --
 source3/modules/vfs_fruit.c           | 44 +++++++++++++++++++++++----
 2 files changed, 38 insertions(+), 9 deletions(-)

diff --git a/selftest/knownfail.d/samba3.vfs.fruit b/selftest/knownfail.d/samba3.vfs.fruit
index 7a110c556f9..d401b2d9a88 100644
--- a/selftest/knownfail.d/samba3.vfs.fruit
+++ b/selftest/knownfail.d/samba3.vfs.fruit
@@ -1,6 +1,3 @@
 ^samba3.vfs.fruit streams_depot.OS X AppleDouble file conversion\(nt4_dc\)
 ^samba3.vfs.fruit streams_depot.OS X AppleDouble file conversion without embedded xattr\(nt4_dc\)
 ^samba3.vfs.fruit_netatalk.locking conflict\(nt4_dc\)
-^samba3.vfs.fruit metadata_netatalk.setinfo eof stream\(nt4_dc\)
-^samba3.vfs.fruit metadata_stream.setinfo eof stream\(nt4_dc\)
-^samba3.vfs.fruit streams_depot.setinfo eof stream\(nt4_dc\)
diff --git a/source3/modules/vfs_fruit.c b/source3/modules/vfs_fruit.c
index ad7a364b699..e5f6e76352c 100644
--- a/source3/modules/vfs_fruit.c
+++ b/source3/modules/vfs_fruit.c
@@ -5441,6 +5441,36 @@ static NTSTATUS fruit_streaminfo_rsrc(vfs_handle_struct *handle,
 	return status;
 }
 
+static void fruit_filter_empty_streams(unsigned int *pnum_streams,
+				       struct stream_struct **pstreams)
+{
+	unsigned num_streams = *pnum_streams;
+	struct stream_struct *streams = *pstreams;
+	unsigned i = 0;
+
+	if (!global_fruit_config.nego_aapl) {
+		return;
+	}
+
+	while (i < num_streams) {
+		struct smb_filename smb_fname = (struct smb_filename) {
+			.stream_name = streams[i].name,
+		};
+
+		if (is_ntfs_default_stream_smb_fname(&smb_fname)
+		    || streams[i].size > 0)
+		{
+			i++;
+			continue;
+		}
+
+		streams[i] = streams[num_streams - 1];
+		num_streams--;
+	}
+
+	*pnum_streams = num_streams;
+}
+
 static NTSTATUS fruit_streaminfo(vfs_handle_struct *handle,
 				 struct files_struct *fsp,
 				 const struct smb_filename *smb_fname,
@@ -5462,6 +5492,8 @@ static NTSTATUS fruit_streaminfo(vfs_handle_struct *handle,
 		return status;
 	}
 
+	fruit_filter_empty_streams(pnum_streams, pstreams);
+
 	status = fruit_streaminfo_meta(handle, fsp, smb_fname,
 				       mem_ctx, pnum_streams, pstreams);
 	if (!NT_STATUS_IS_OK(status)) {
@@ -5736,13 +5768,13 @@ static NTSTATUS fruit_create_file(vfs_handle_struct *handle,
 	 * Cf the vfs_fruit torture tests in test_rfork_create().
 	 */
 	if (global_fruit_config.nego_aapl &&
-	    is_afpresource_stream(fsp->fsp_name) &&
-	    create_disposition == FILE_OPEN)
+	    create_disposition == FILE_OPEN &&
+	    smb_fname->st.st_ex_size == 0 &&
+	    is_ntfs_stream_smb_fname(smb_fname) &&
+	    !(is_ntfs_default_stream_smb_fname(smb_fname)))
 	{
-		if (fsp->fsp_name->st.st_ex_size == 0) {
-			status = NT_STATUS_OBJECT_NAME_NOT_FOUND;
-			goto fail;
-		}
+		status = NT_STATUS_OBJECT_NAME_NOT_FOUND;
+		goto fail;
 	}
 
 	if (is_ntfs_stream_smb_fname(smb_fname)
-- 
2.17.2


From 16f7e59a1bd7fc7e36c694fd7f20993418c627b5 Mon Sep 17 00:00:00 2001
From: Ralph Boehme <slow at samba.org>
Date: Wed, 10 Oct 2018 18:45:56 +0200
Subject: [PATCH 21/35] s4:torture/util: add torture_smb2_open()

This seems to be missing: a simple wrapper to just open a file without
fancy options.

Bug: https://bugzilla.samba.org/show_bug.cgi?id=13646

Signed-off-by: Ralph Boehme <slow at samba.org>
---
 source4/torture/smb2/util.c | 30 ++++++++++++++++++++++++++++++
 1 file changed, 30 insertions(+)

diff --git a/source4/torture/smb2/util.c b/source4/torture/smb2/util.c
index 65090b0e8b7..ac8a0d5df77 100644
--- a/source4/torture/smb2/util.c
+++ b/source4/torture/smb2/util.c
@@ -527,6 +527,36 @@ NTSTATUS torture_smb2_testfile(struct smb2_tree *tree, const char *fname,
 					    SEC_RIGHTS_FILE_ALL);
 }
 
+/*
+  create and return a handle to a test file
+  with a specific access mask
+*/
+NTSTATUS torture_smb2_open(struct smb2_tree *tree,
+			   const char *fname,
+			   uint32_t desired_access,
+			   struct smb2_handle *handle)
+{
+	struct smb2_create io;
+	NTSTATUS status;
+
+	io = (struct smb2_create) {
+		.in.fname = fname,
+		.in.desired_access = desired_access,
+		.in.file_attributes = FILE_ATTRIBUTE_NORMAL,
+		.in.create_disposition = NTCREATEX_DISP_OPEN,
+		.in.share_access = NTCREATEX_SHARE_ACCESS_MASK,
+	};
+
+	status = smb2_create(tree, tree, &io);
+	if (!NT_STATUS_IS_OK(status)) {
+		return status;
+	}
+
+	*handle = io.out.file.handle;
+
+	return NT_STATUS_OK;
+}
+
 /*
   create and return a handle to a test directory
   with specific desired access
-- 
2.17.2


From 6e025de7db3e5a77766d5504f1b0353f7ff9201e Mon Sep 17 00:00:00 2001
From: Ralph Boehme <slow at samba.org>
Date: Thu, 11 Oct 2018 17:14:50 +0200
Subject: [PATCH 22/35] s4:torture/vfs/fruit: add check_stream_list_handle()

Bug: https://bugzilla.samba.org/show_bug.cgi?id=13646

Signed-off-by: Ralph Boehme <slow at samba.org>
---
 source4/torture/vfs/fruit.c | 62 +++++++++++++++++++++++++++++++++++++
 1 file changed, 62 insertions(+)

diff --git a/source4/torture/vfs/fruit.c b/source4/torture/vfs/fruit.c
index 11c287aeb4c..ef754823731 100644
--- a/source4/torture/vfs/fruit.c
+++ b/source4/torture/vfs/fruit.c
@@ -3082,6 +3082,68 @@ static bool check_stream_list(struct smb2_tree *tree,
 	return ret;
 }
 
+#if 0
+static bool check_stream_list_handle(struct smb2_tree *tree,
+				     struct torture_context *tctx,
+				     struct smb2_handle h,
+				     int num_exp,
+				     const char **exp,
+				     bool is_dir)
+{
+	bool ret = true;
+	union smb_fileinfo finfo;
+	NTSTATUS status;
+	int i;
+	TALLOC_CTX *tmp_ctx = talloc_new(tctx);
+	char **exp_sort;
+	struct stream_struct *stream_sort;
+
+	torture_assert_goto(tctx, tmp_ctx != NULL, ret, done,
+			    "talloc_new failed\n");
+
+	finfo = (union smb_fileinfo) {
+		.stream_info.level = RAW_FILEINFO_STREAM_INFORMATION,
+		.stream_info.in.file.handle = h,
+	};
+
+	status = smb2_getinfo_file(tree, tctx, &finfo);
+	torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
+					"get stream info\n");
+
+	torture_assert_int_equal_goto(tctx, finfo.stream_info.out.num_streams,
+				      num_exp, ret, done, "stream count\n");
+
+	if (num_exp == 0) {
+		TALLOC_FREE(tmp_ctx);
+		goto done;
+	}
+
+	exp_sort = talloc_memdup(tmp_ctx, exp, num_exp * sizeof(*exp));
+	torture_assert_goto(tctx, exp_sort != NULL, ret, done, __location__);
+
+	TYPESAFE_QSORT(exp_sort, num_exp, qsort_string);
+
+	stream_sort = talloc_memdup(tmp_ctx, finfo.stream_info.out.streams,
+				    finfo.stream_info.out.num_streams *
+				    sizeof(*stream_sort));
+	torture_assert_goto(tctx, stream_sort != NULL, ret, done, __location__);
+
+	TYPESAFE_QSORT(stream_sort, finfo.stream_info.out.num_streams, qsort_stream);
+
+	for (i=0; i<num_exp; i++) {
+		torture_comment(tctx, "i[%d] exp[%s] got[%s]\n",
+				i, exp_sort[i], stream_sort[i].stream_name.s);
+		torture_assert_str_equal_goto(tctx, stream_sort[i].stream_name.s,
+					      exp_sort[i], ret, done,
+					      "stream name\n");
+	}
+
+done:
+	TALLOC_FREE(tmp_ctx);
+	return ret;
+}
+#endif
+
 /*
   test stream names
 */
-- 
2.17.2


From 7dabe93b5168a9ce881b4c9e294786a7c3e3902d Mon Sep 17 00:00:00 2001
From: Ralph Boehme <slow at samba.org>
Date: Mon, 22 Oct 2018 16:21:21 +0200
Subject: [PATCH 23/35] s4:torture/vfs/fruit: add test "empty_stream"

One to rule them all: consistently test critical operations on all
streams relevant to macOS clients: the FinderInfo stream, the Resource
Fork stream and an arbitrary stream that macOS maps to xattrs when
written to on a macOS SMB server.

Bug: https://bugzilla.samba.org/show_bug.cgi?id=13646

Signed-off-by: Ralph Boehme <slow at samba.org>
---
 selftest/knownfail.d/samba3.vfs.fruit |   3 +
 source4/torture/vfs/fruit.c           | 613 +++++++++++++++++++++++++-
 2 files changed, 614 insertions(+), 2 deletions(-)

diff --git a/selftest/knownfail.d/samba3.vfs.fruit b/selftest/knownfail.d/samba3.vfs.fruit
index d401b2d9a88..e44aff6d4b0 100644
--- a/selftest/knownfail.d/samba3.vfs.fruit
+++ b/selftest/knownfail.d/samba3.vfs.fruit
@@ -1,3 +1,6 @@
 ^samba3.vfs.fruit streams_depot.OS X AppleDouble file conversion\(nt4_dc\)
 ^samba3.vfs.fruit streams_depot.OS X AppleDouble file conversion without embedded xattr\(nt4_dc\)
 ^samba3.vfs.fruit_netatalk.locking conflict\(nt4_dc\)
+^samba3.vfs.fruit metadata_netatalk.empty_stream\(nt4_dc\)
+^samba3.vfs.fruit metadata_stream.empty_stream\(nt4_dc\)
+^samba3.vfs.fruit streams_depot.empty_stream\(nt4_dc\)
diff --git a/source4/torture/vfs/fruit.c b/source4/torture/vfs/fruit.c
index ef754823731..236ce6c45f6 100644
--- a/source4/torture/vfs/fruit.c
+++ b/source4/torture/vfs/fruit.c
@@ -3082,7 +3082,6 @@ static bool check_stream_list(struct smb2_tree *tree,
 	return ret;
 }
 
-#if 0
 static bool check_stream_list_handle(struct smb2_tree *tree,
 				     struct torture_context *tctx,
 				     struct smb2_handle h,
@@ -3142,7 +3141,6 @@ static bool check_stream_list_handle(struct smb2_tree *tree,
 	TALLOC_FREE(tmp_ctx);
 	return ret;
 }
-#endif
 
 /*
   test stream names
@@ -5264,6 +5262,616 @@ static bool test_setinfo_stream_eof(struct torture_context *tctx,
 	return ret;
 }
 
+#define MAX_STREAMS 16
+
+struct tcase {
+	const char *name;
+	uint32_t access;
+	const char *write_data;
+	size_t write_size;
+	struct tcase_results {
+		size_t size;
+		NTSTATUS initial_status;
+		NTSTATUS final_status;
+		int num_streams_open_handle;
+		const char *streams_open_handle[MAX_STREAMS];
+		int num_streams_closed_handle;
+		const char *streams_closed_handle[MAX_STREAMS];
+	} create, write, overwrite, eof, doc;
+};
+
+typedef enum {T_CREATE, T_WRITE, T_OVERWRITE, T_EOF, T_DOC} subtcase_t;
+
+static bool test_empty_stream_do_checks(
+	struct torture_context *tctx,
+	struct smb2_tree *tree,
+	struct smb2_tree *tree2,
+	struct tcase *tcase,
+	TALLOC_CTX *mem_ctx,
+	struct smb2_handle baseh,
+	struct smb2_handle streamh,
+	subtcase_t subcase)
+{
+	bool ret = false;
+	NTSTATUS status;
+	struct smb2_handle h1;
+	union smb_fileinfo finfo;
+	struct tcase_results *tcase_results = NULL;
+
+	switch (subcase) {
+	case T_CREATE:
+		tcase_results = &tcase->create;
+		break;
+	case T_OVERWRITE:
+		tcase_results = &tcase->overwrite;
+		break;
+	case T_WRITE:
+		tcase_results = &tcase->write;
+		break;
+	case T_EOF:
+		tcase_results = &tcase->eof;
+		break;
+	case T_DOC:
+		tcase_results = &tcase->doc;
+		break;
+	}
+
+	finfo = (union smb_fileinfo) {
+		.generic.level = RAW_FILEINFO_STANDARD_INFORMATION,
+		.generic.in.file.handle = streamh,
+	};
+
+	/*
+	 * Test: check size, same client
+	 */
+
+	status = smb2_getinfo_file(tree, mem_ctx, &finfo);
+	torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
+					"torture_smb2_testfile failed\n");
+
+	torture_assert_int_equal_goto(tctx, finfo.standard_info.out.size,
+				      tcase_results->size,
+				      ret, done, "Wrong size\n");
+
+	/*
+	 * Test: open, same client
+	 */
+
+	status = torture_smb2_open(tree, tcase->name,
+				   SEC_FILE_READ_ATTRIBUTE, &h1);
+	torture_assert_ntstatus_equal_goto(tctx, status,
+					   tcase_results->initial_status,
+					   ret, done,
+					   "smb2_create failed\n");
+	if (NT_STATUS_IS_OK(status)) {
+		status = smb2_util_close(tree, h1);
+		torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
+						"smb2_util_close failed\n");
+	}
+
+	/*
+	 * Test: check streams, same client
+	 */
+
+	ret = check_stream_list_handle(tree, tctx, baseh,
+				       tcase_results->num_streams_open_handle,
+				       tcase_results->streams_open_handle,
+				       false);
+	torture_assert_goto(tctx, ret == true, ret, done, "Bad streams");
+
+	/*
+	 * Test: open, different client
+	 */
+
+	status = torture_smb2_open(tree2, tcase->name,
+				   SEC_FILE_READ_ATTRIBUTE, &h1);
+	torture_assert_ntstatus_equal_goto(tctx, status,
+					   tcase_results->initial_status,
+					   ret, done,
+					   "smb2_create failed\n");
+	if (NT_STATUS_IS_OK(status)) {
+		finfo = (union smb_fileinfo) {
+			.generic.level = RAW_FILEINFO_STANDARD_INFORMATION,
+			.generic.in.file.handle = h1,
+		};
+
+		/*
+		 * Test: check size, different client
+		 */
+
+		status = smb2_getinfo_file(tree2, mem_ctx, &finfo);
+		torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
+						"smb2_getinfo_file failed\n");
+
+		torture_assert_int_equal_goto(tctx, finfo.standard_info.out.size,
+					      tcase_results->size,
+					      ret, done, "Wrong size\n");
+
+		/*
+		 * Test: check streams, different client
+		 */
+
+		ret = check_stream_list(tree2, tctx, BASEDIR "\\file",
+					tcase_results->num_streams_open_handle,
+					tcase_results->streams_open_handle,
+					false);
+		torture_assert_goto(tctx, ret == true, ret, done, "Bad streams");
+
+		status = smb2_util_close(tree2, h1);
+		torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
+						"smb2_util_close failed\n");
+	}
+
+	status = smb2_util_close(tree, streamh);
+	torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
+					"smb2_util_close failed\n");
+
+	/*
+	 * Test: open after close, same client
+	 */
+
+	status = torture_smb2_open(tree, tcase->name,
+				   SEC_FILE_READ_DATA, &h1);
+	torture_assert_ntstatus_equal_goto(tctx, status,
+					   tcase_results->final_status,
+					   ret, done,
+					   "smb2_create failed\n");
+	if (NT_STATUS_IS_OK(status)) {
+		status = smb2_util_close(tree, h1);
+		torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
+						"smb2_util_close failed\n");
+	}
+
+	/*
+	 * Test: open after close, different client
+	 */
+
+	status = torture_smb2_open(tree2, tcase->name,
+				   SEC_FILE_READ_DATA, &h1);
+	torture_assert_ntstatus_equal_goto(tctx, status,
+					   tcase_results->final_status,
+					   ret, done,
+					   "smb2_create failed\n");
+	if (NT_STATUS_IS_OK(status)) {
+		status = smb2_util_close(tree2, h1);
+		torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
+						"smb2_util_close failed\n");
+	}
+
+	/*
+	 * Test: check streams after close, same client
+	 */
+
+	ret = check_stream_list_handle(tree, tctx, baseh,
+				       tcase_results->num_streams_closed_handle,
+				       tcase_results->streams_closed_handle,
+				       false);
+	torture_assert_goto(tctx, ret == true, ret, done, "Bad streams");
+
+	ret = true;
+
+done:
+	smb2_util_close(tree, streamh);
+	smb2_util_close(tree, baseh);
+	return ret;
+}
+
+static bool test_empty_stream_do_one(
+	struct torture_context *tctx,
+	struct smb2_tree *tree,
+	struct smb2_tree *tree2,
+	struct tcase *tcase)
+{
+	bool ret = false;
+	NTSTATUS status;
+	struct smb2_handle baseh;
+	struct smb2_handle streamh;
+	struct smb2_create create;
+	union smb_setfileinfo sfinfo;
+	TALLOC_CTX *mem_ctx = talloc_new(tctx);
+
+	torture_comment(tctx, "Testing stream [%s]\n", tcase->name);
+
+	torture_assert_goto(tctx, mem_ctx != NULL, ret, done, "talloc_new\n");
+
+	/*
+	 * Subtest: create
+	 */
+	torture_comment(tctx, "Subtest: T_CREATE\n");
+
+	status = smb2_util_unlink(tree, BASEDIR "\\file");
+	torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
+					"smb2_util_unlink failed\n");
+
+	status = torture_smb2_testfile_access(tree, BASEDIR "\\file",
+					      &baseh, SEC_FILE_ALL);
+	torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
+					"torture_smb2_testfile_access failed\n");
+
+	status = torture_smb2_testfile_access(tree, tcase->name, &streamh,
+					      tcase->access);
+	torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
+					"torture_smb2_testfile_access failed\n");
+
+	ret = test_empty_stream_do_checks(tctx, tree, tree2, tcase,
+					  mem_ctx, baseh, streamh, T_CREATE);
+	torture_assert_goto(tctx, ret, ret, done, "test failed\n");
+
+	if (!(tcase->access & SEC_FILE_WRITE_DATA)) {
+		/*
+		 * All subsequent tests require write access
+		 */
+		ret = true;
+		goto done;
+	}
+
+	/*
+	 * Subtest: create and write
+	 */
+	torture_comment(tctx, "Subtest: T_WRITE\n");
+
+	status = smb2_util_unlink(tree, BASEDIR "\\file");
+	torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
+					"smb2_util_unlink failed\n");
+
+	status = torture_smb2_testfile_access(tree, BASEDIR "\\file",
+					      &baseh, SEC_FILE_ALL);
+	torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
+					"torture_smb2_testfile_access failed\n");
+
+	status = torture_smb2_testfile_access(tree, tcase->name, &streamh,
+					      tcase->access);
+	torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
+					"torture_smb2_testfile_access failed\n");
+
+	status = smb2_util_write(tree, streamh, tcase->write_data, 0,
+				 tcase->write_size);
+	torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
+					"torture_smb2_open failed\n");
+
+	ret = test_empty_stream_do_checks(tctx, tree, tree2, tcase,
+					  mem_ctx, baseh, streamh, T_WRITE);
+	torture_assert_goto(tctx, ret, ret, done, "test failed\n");
+
+	/*
+	 * Subtest: overwrite
+	 */
+	torture_comment(tctx, "Subtest: T_OVERWRITE\n");
+
+	status = smb2_util_unlink(tree, BASEDIR "\\file");
+	torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
+					"smb2_util_unlink failed\n");
+
+	status = torture_smb2_testfile_access(tree, BASEDIR "\\file",
+					      &baseh, SEC_FILE_ALL);
+	torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
+					"torture_smb2_testfile_access failed\n");
+
+	create = (struct smb2_create) {
+		.in.desired_access = SEC_FILE_ALL,
+		.in.share_access = NTCREATEX_SHARE_ACCESS_MASK,
+		.in.file_attributes = FILE_ATTRIBUTE_NORMAL,
+		.in.create_disposition = NTCREATEX_DISP_OVERWRITE_IF,
+		.in.fname = tcase->name,
+	};
+
+	status = smb2_create(tree, tctx, &create);
+	torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
+					"torture_smb2_testfile failed\n");
+	streamh = create.out.file.handle;
+
+	ret = test_empty_stream_do_checks(tctx, tree, tree2, tcase,
+					  mem_ctx, baseh, streamh, T_OVERWRITE);
+	torture_assert_goto(tctx, ret, ret, done, "test failed\n");
+
+	/*
+	 * Subtest: setinfo EOF 0
+	 */
+	torture_comment(tctx, "Subtest: T_EOF\n");
+
+	status = smb2_util_unlink(tree, BASEDIR "\\file");
+	torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
+					"smb2_util_unlink failed\n");
+
+	status = torture_smb2_testfile_access(tree, BASEDIR "\\file",
+					      &baseh, SEC_FILE_ALL);
+	torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
+					"torture_smb2_testfile_access failed\n");
+
+	status = torture_smb2_testfile_access(tree, tcase->name, &streamh,
+					      tcase->access);
+	torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
+					"torture_smb2_testfile_access failed\n");
+
+	status = smb2_util_write(tree, streamh, tcase->write_data, 0,
+				 tcase->write_size);
+	torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
+					"torture_smb2_open failed\n");
+
+	sfinfo = (union smb_setfileinfo) {
+		.end_of_file_info.level = RAW_SFILEINFO_END_OF_FILE_INFORMATION,
+		.end_of_file_info.in.file.handle = streamh,
+		.end_of_file_info.in.size = 0,
+	};
+	status = smb2_setinfo_file(tree, &sfinfo);
+	torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
+					"set eof 0 failed\n");
+
+	ret = test_empty_stream_do_checks(tctx, tree, tree2, tcase,
+					  mem_ctx, baseh, streamh, T_EOF);
+	torture_assert_goto(tctx, ret, ret, done, "test failed\n");
+
+	/*
+	 * Subtest: delete-on-close
+	 */
+	torture_comment(tctx, "Subtest: T_DOC\n");
+
+	status = smb2_util_unlink(tree, BASEDIR "\\file");
+	torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
+					"smb2_util_unlink failed\n");
+
+	status = torture_smb2_testfile_access(tree, BASEDIR "\\file",
+					      &baseh, SEC_FILE_ALL);
+	torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
+					"torture_smb2_testfile_access failed\n");
+
+	status = torture_smb2_testfile_access(tree, tcase->name, &streamh,
+					      tcase->access);
+	torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
+					"torture_smb2_testfile_access failed\n");
+
+	status = smb2_util_write(tree, streamh, tcase->write_data, 0,
+				 tcase->write_size);
+	torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
+					"torture_smb2_open failed\n");
+
+	sfinfo = (union smb_setfileinfo) {
+		.disposition_info.level = RAW_SFILEINFO_DISPOSITION_INFORMATION,
+		.disposition_info.in.file.handle = streamh,
+		.disposition_info.in.delete_on_close = true,
+	};
+	status = smb2_setinfo_file(tree, &sfinfo);
+	torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
+					"set eof 0 failed\n");
+
+	ret = test_empty_stream_do_checks(tctx, tree, tree2, tcase,
+					  mem_ctx, baseh, streamh,
+					  T_DOC);
+	torture_assert_goto(tctx, ret, ret, done, "test failed\n");
+
+	ret = true;
+
+done:
+	smb2_util_close(tree, baseh);
+	TALLOC_FREE(mem_ctx);
+	return ret;
+}
+
+static bool test_empty_stream(struct torture_context *tctx,
+			      struct smb2_tree *tree)
+{
+	struct smb2_tree *tree2 = NULL;
+	struct tcase *tcase = NULL;
+	const char *fname = BASEDIR "\\file";
+	struct smb2_handle h1;
+	bool ret = true;
+	NTSTATUS status;
+	AfpInfo ai = (AfpInfo) {
+		.afpi_Signature = AFP_Signature,
+		.afpi_Version = AFP_Version,
+		.afpi_BackupTime = AFP_BackupTime,
+		.afpi_FinderInfo = "FOO BAR ",
+	};
+	char *ai_blob = torture_afpinfo_pack(tctx, &ai);
+	struct tcase tcase_afpinfo_ro = (struct tcase) {
+		.name = BASEDIR "\\file" AFPINFO_STREAM,
+		.access = SEC_FILE_READ_DATA|SEC_FILE_READ_ATTRIBUTE,
+		.create.size = 60,
+		.create.initial_status = NT_STATUS_OBJECT_NAME_NOT_FOUND,
+		.create.final_status = NT_STATUS_OBJECT_NAME_NOT_FOUND,
+		.create.num_streams_open_handle = 1,
+		.create.num_streams_closed_handle = 1,
+		.create.streams_open_handle = {"::$DATA"},
+		.create.streams_closed_handle = {"::$DATA"},
+	};
+	struct tcase tcase_afpinfo_rw = (struct tcase) {
+		.name = BASEDIR "\\file" AFPINFO_STREAM,
+		.access = SEC_FILE_READ_DATA|SEC_FILE_READ_ATTRIBUTE|SEC_FILE_WRITE_DATA|SEC_STD_DELETE,
+		.write_data = ai_blob,
+		.write_size = AFP_INFO_SIZE,
+		.create.size = 60,
+		.create.initial_status = NT_STATUS_OBJECT_NAME_NOT_FOUND,
+		.create.final_status = NT_STATUS_OBJECT_NAME_NOT_FOUND,
+		.create.num_streams_open_handle = 1,
+		.create.num_streams_closed_handle = 1,
+		.create.streams_open_handle = {"::$DATA"},
+		.create.streams_closed_handle = {"::$DATA"},
+		.write.size = 60,
+		.write.initial_status = NT_STATUS_OK,
+		.write.final_status = NT_STATUS_OK,
+		.write.num_streams_open_handle = 2,
+		.write.num_streams_closed_handle = 2,
+		.write.streams_open_handle = {"::$DATA", AFPINFO_STREAM},
+		.write.streams_closed_handle = {"::$DATA", AFPINFO_STREAM},
+		.overwrite.size = 60,
+		.overwrite.initial_status = NT_STATUS_OBJECT_NAME_NOT_FOUND,
+		.overwrite.final_status = NT_STATUS_OBJECT_NAME_NOT_FOUND,
+		.overwrite.num_streams_open_handle = 1,
+		.overwrite.num_streams_closed_handle = 1,
+		.overwrite.streams_open_handle = {"::$DATA"},
+		.overwrite.streams_closed_handle = {"::$DATA"},
+		.eof.size = 60,
+		.eof.initial_status = NT_STATUS_OK,
+		.eof.final_status = NT_STATUS_OK,
+		.eof.num_streams_open_handle = 2,
+		.eof.num_streams_closed_handle = 2,
+		.eof.streams_open_handle = {"::$DATA", AFPINFO_STREAM},
+		.eof.streams_closed_handle = {"::$DATA", AFPINFO_STREAM},
+		.doc.size = 60,
+		.doc.initial_status = NT_STATUS_DELETE_PENDING,
+		.doc.final_status = NT_STATUS_OBJECT_NAME_NOT_FOUND,
+		.doc.num_streams_open_handle = 2,
+		.doc.num_streams_closed_handle = 1,
+		.doc.streams_open_handle = {"::$DATA", AFPINFO_STREAM},
+		.doc.streams_closed_handle = {"::$DATA"},
+	};
+
+	struct tcase tcase_afpresource_ro = (struct tcase) {
+		.name = BASEDIR "\\file" AFPRESOURCE_STREAM,
+		.access = SEC_FILE_READ_DATA|SEC_FILE_READ_ATTRIBUTE,
+		.create.size = 0,
+		.create.initial_status = NT_STATUS_OBJECT_NAME_NOT_FOUND,
+		.create.final_status = NT_STATUS_OBJECT_NAME_NOT_FOUND,
+		.create.num_streams_open_handle = 1,
+		.create.num_streams_closed_handle = 1,
+		.create.streams_open_handle = {"::$DATA"},
+		.create.streams_closed_handle = {"::$DATA"},
+	};
+	struct tcase tcase_afpresource_rw = (struct tcase) {
+		.name = BASEDIR "\\file" AFPRESOURCE_STREAM,
+		.access = SEC_FILE_READ_DATA|SEC_FILE_READ_ATTRIBUTE|SEC_FILE_WRITE_DATA|SEC_STD_DELETE,
+		.write_data = "foo",
+		.write_size = 3,
+		.create.size = 0,
+		.create.initial_status = NT_STATUS_OBJECT_NAME_NOT_FOUND,
+		.create.final_status = NT_STATUS_OBJECT_NAME_NOT_FOUND,
+		.create.num_streams_open_handle = 1,
+		.create.num_streams_closed_handle = 1,
+		.create.streams_open_handle = {"::$DATA"},
+		.create.streams_closed_handle = {"::$DATA"},
+		.write.size = 3,
+		.write.initial_status = NT_STATUS_OK,
+		.write.final_status = NT_STATUS_OK,
+		.write.num_streams_open_handle = 2,
+		.write.num_streams_closed_handle = 2,
+		.write.streams_open_handle = {"::$DATA", AFPRESOURCE_STREAM},
+		.write.streams_closed_handle = {"::$DATA", AFPRESOURCE_STREAM},
+		.overwrite.size = 0,
+		.overwrite.initial_status = NT_STATUS_OBJECT_NAME_NOT_FOUND,
+		.overwrite.final_status = NT_STATUS_OBJECT_NAME_NOT_FOUND,
+		.overwrite.num_streams_open_handle = 1,
+		.overwrite.num_streams_closed_handle = 1,
+		.overwrite.streams_open_handle = {"::$DATA"},
+		.overwrite.streams_closed_handle = {"::$DATA"},
+		.eof.size = 0,
+		.eof.initial_status = NT_STATUS_OBJECT_NAME_NOT_FOUND,
+		.eof.final_status = NT_STATUS_OBJECT_NAME_NOT_FOUND,
+		.eof.num_streams_open_handle = 1,
+		.eof.num_streams_closed_handle = 1,
+		.eof.streams_open_handle = {"::$DATA"},
+		.eof.streams_closed_handle = {"::$DATA"},
+		.doc.size = 3,
+		.doc.initial_status = NT_STATUS_DELETE_PENDING,
+		.doc.final_status = NT_STATUS_OK,
+		.doc.num_streams_open_handle = 2,
+		.doc.num_streams_closed_handle = 2,
+		.doc.streams_open_handle = {"::$DATA", AFPRESOURCE_STREAM},
+		.doc.streams_closed_handle = {"::$DATA", AFPRESOURCE_STREAM},
+	};
+
+	struct tcase tcase_foo_ro = (struct tcase) {
+		.name = BASEDIR "\\file:foo",
+		.access = SEC_FILE_READ_DATA|SEC_FILE_READ_ATTRIBUTE,
+		.write_data = "foo",
+		.write_size = 3,
+		.create.size = 0,
+		.create.initial_status = NT_STATUS_OBJECT_NAME_NOT_FOUND,
+		.create.final_status = NT_STATUS_OBJECT_NAME_NOT_FOUND,
+		.create.num_streams_open_handle = 1,
+		.create.num_streams_closed_handle = 1,
+		.create.streams_open_handle = {"::$DATA"},
+		.create.streams_closed_handle = {"::$DATA"},
+	};
+
+	struct tcase tcase_foo_rw = (struct tcase) {
+		.name = BASEDIR "\\file:foo",
+		.access = SEC_FILE_READ_DATA|SEC_FILE_READ_ATTRIBUTE|SEC_FILE_WRITE_DATA|SEC_STD_DELETE,
+		.write_data = "foo",
+		.write_size = 3,
+		.create.size = 0,
+		.create.initial_status = NT_STATUS_OBJECT_NAME_NOT_FOUND,
+		.create.final_status = NT_STATUS_OBJECT_NAME_NOT_FOUND,
+		.create.num_streams_open_handle = 1,
+		.create.num_streams_closed_handle = 1,
+		.create.streams_open_handle = {"::$DATA"},
+		.create.streams_closed_handle = {"::$DATA"},
+		.write.size = 3,
+		.write.initial_status = NT_STATUS_OK,
+		.write.final_status = NT_STATUS_OK,
+		.write.num_streams_open_handle = 2,
+		.write.num_streams_closed_handle = 2,
+		.write.streams_open_handle = {"::$DATA", ":foo:$DATA"},
+		.write.streams_closed_handle = {"::$DATA", ":foo:$DATA"},
+		.overwrite.size = 0,
+		.overwrite.initial_status = NT_STATUS_OBJECT_NAME_NOT_FOUND,
+		.overwrite.final_status = NT_STATUS_OBJECT_NAME_NOT_FOUND,
+		.overwrite.num_streams_open_handle = 1,
+		.overwrite.num_streams_closed_handle = 1,
+		.overwrite.streams_open_handle = {"::$DATA"},
+		.overwrite.streams_closed_handle = {"::$DATA"},
+		.eof.size = 0,
+		.eof.initial_status = NT_STATUS_OBJECT_NAME_NOT_FOUND,
+		.eof.final_status = NT_STATUS_OBJECT_NAME_NOT_FOUND,
+		.eof.num_streams_open_handle = 1,
+		.eof.num_streams_closed_handle = 1,
+		.eof.streams_open_handle = {"::$DATA"},
+		.eof.streams_closed_handle = {"::$DATA"},
+		.doc.size = 3,
+		.doc.initial_status = NT_STATUS_DELETE_PENDING,
+		.doc.final_status = NT_STATUS_OBJECT_NAME_NOT_FOUND,
+		.doc.num_streams_open_handle = 2,
+		.doc.num_streams_closed_handle = 1,
+		.doc.streams_open_handle = {"::$DATA", ":foo:$DATA"},
+		.doc.streams_closed_handle = {"::$DATA"},
+	};
+
+	struct tcase tcases[] = {
+		tcase_afpinfo_ro,
+		tcase_afpinfo_rw,
+		tcase_afpresource_ro,
+		tcase_afpresource_rw,
+		tcase_foo_ro,
+		tcase_foo_rw,
+		{NULL}
+	};
+
+	ret = torture_smb2_connection(tctx, &tree2);
+	torture_assert_goto(tctx, ret == true, ret, done,
+			    "torture_smb2_connection failed\n");
+
+	ret = enable_aapl(tctx, tree);
+	torture_assert(tctx, ret == true, "enable_aapl failed\n");
+
+	ret = enable_aapl(tctx, tree2);
+	torture_assert(tctx, ret == true, "enable_aapl failed\n");
+
+	smb2_deltree(tree, BASEDIR);
+
+	status = torture_smb2_testdir(tree, BASEDIR, &h1);
+	torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
+					"torture_smb2_testdir\n");
+	smb2_util_close(tree, h1);
+
+	for (tcase = &tcases[0]; tcase->name != NULL; tcase++) {
+		ret = torture_setup_file(tctx, tree, fname, false);
+		torture_assert_goto(tctx, ret == true, ret, done,
+				    "torture_setup_file failed\n");
+
+		ret = test_empty_stream_do_one(tctx, tree, tree2, tcase);
+		torture_assert_goto(tctx, ret == true, ret, done,
+				    "subtest failed\n");
+
+		status = smb2_util_unlink(tree, fname);
+		torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
+						"smb2_util_unlink failed\n");
+	}
+
+done:
+	smb2_deltree(tree, BASEDIR);
+	TALLOC_FREE(tree2);
+	return ret;
+}
+
 /*
  * Note: This test depends on "vfs objects = catia fruit streams_xattr".  For
  * some tests torture must be run on the host it tests and takes an additional
@@ -5307,6 +5915,7 @@ struct torture_suite *torture_vfs_fruit(TALLOC_CTX *ctx)
 	torture_suite_add_1smb2_test(suite, "OS X AppleDouble file conversion", test_adouble_conversion);
 	torture_suite_add_1smb2_test(suite, "NFS ACE entries", test_nfs_aces);
 	torture_suite_add_1smb2_test(suite, "OS X AppleDouble file conversion without embedded xattr", test_adouble_conversion_wo_xattr);
+	torture_suite_add_1smb2_test(suite, "empty_stream", test_empty_stream);
 
 	return suite;
 }
-- 
2.17.2


From f5828d90d26cf1fe2f55414019165c7012f51053 Mon Sep 17 00:00:00 2001
From: Ralph Boehme <slow at samba.org>
Date: Wed, 17 Oct 2018 19:07:11 +0200
Subject: [PATCH 24/35] vfs_fruit: update and add some debugging of dev/ino

Aids in debugging dev/ino mismatch failures in open_file_ntcreate.

Bug: https://bugzilla.samba.org/show_bug.cgi?id=13646

Signed-off-by: Ralph Boehme <slow at samba.org>
---
 source3/modules/vfs_fruit.c | 13 +++++++++++--
 1 file changed, 11 insertions(+), 2 deletions(-)

diff --git a/source3/modules/vfs_fruit.c b/source3/modules/vfs_fruit.c
index e5f6e76352c..e264440b734 100644
--- a/source3/modules/vfs_fruit.c
+++ b/source3/modules/vfs_fruit.c
@@ -2217,6 +2217,10 @@ static SMB_INO_T fruit_inode(const SMB_STRUCT_STAT *sbuf, const char *sname)
 	SMB_INO_T result;
 	char *upper_sname;
 
+	DBG_DEBUG("fruit_inode called for 0x%jx/0x%jx [%s]\n",
+		  (uintmax_t)sbuf->st_ex_dev,
+		  (uintmax_t)sbuf->st_ex_ino, sname);
+
 	upper_sname = talloc_strdup_upper(talloc_tos(), sname);
 	SMB_ASSERT(upper_sname != NULL);
 
@@ -2234,8 +2238,8 @@ static SMB_INO_T fruit_inode(const SMB_STRUCT_STAT *sbuf, const char *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));
+	DBG_DEBUG("fruit_inode \"%s\": ino=0x%jx\n",
+		  sname, (uintmax_t)result);
 
 	return result;
 }
@@ -4658,6 +4662,11 @@ static int fruit_stat_base(vfs_handle_struct *handle,
 		rc = SMB_VFS_NEXT_LSTAT(handle, smb_fname);
 	}
 	smb_fname->stream_name = tmp_stream_name;
+
+	DBG_DEBUG("fruit_stat_base [%s] dev [0x%jx] ino [0x%jx]\n",
+		  smb_fname->base_name,
+		  (uintmax_t)smb_fname->st.st_ex_dev,
+		  (uintmax_t)smb_fname->st.st_ex_ino);
 	return rc;
 }
 
-- 
2.17.2


From ecb152b8b0072811d119ab96c80894f9797c0084 Mon Sep 17 00:00:00 2001
From: Ralph Boehme <slow at samba.org>
Date: Mon, 15 Oct 2018 18:38:33 +0200
Subject: [PATCH 25/35] vfs_fruit: remove resource fork special casing

Directly unlinking a file with open handles is not good, don't do it.

Bug: https://bugzilla.samba.org/show_bug.cgi?id=13646

Signed-off-by: Ralph Boehme <slow at samba.org>
---
 source3/modules/vfs_fruit.c | 8 --------
 1 file changed, 8 deletions(-)

diff --git a/source3/modules/vfs_fruit.c b/source3/modules/vfs_fruit.c
index e264440b734..5a7967490b7 100644
--- a/source3/modules/vfs_fruit.c
+++ b/source3/modules/vfs_fruit.c
@@ -5579,10 +5579,6 @@ static int fruit_ftruncate_rsrc_xattr(struct vfs_handle_struct *handle,
 				      struct files_struct *fsp,
 				      off_t offset)
 {
-	if (offset == 0) {
-		return SMB_VFS_FREMOVEXATTR(fsp, AFPRESOURCE_EA_NETATALK);
-	}
-
 #ifdef HAVE_ATTROPEN
 	return SMB_VFS_NEXT_FTRUNCATE(handle, fsp, offset);
 #endif
@@ -5630,10 +5626,6 @@ static int fruit_ftruncate_rsrc_stream(struct vfs_handle_struct *handle,
 				       struct files_struct *fsp,
 				       off_t offset)
 {
-	if (offset == 0) {
-		return SMB_VFS_NEXT_UNLINK(handle, fsp->fsp_name);
-	}
-
 	return SMB_VFS_NEXT_FTRUNCATE(handle, fsp, offset);
 }
 
-- 
2.17.2


From 3794f21cf670b72cadd8faae16517246819095c5 Mon Sep 17 00:00:00 2001
From: Ralph Boehme <slow at samba.org>
Date: Mon, 22 Oct 2018 16:56:46 +0200
Subject: [PATCH 26/35] vfs_fruit: add fio->created

fio->created tracks whether a create created a stream.

Bug: https://bugzilla.samba.org/show_bug.cgi?id=13646

Signed-off-by: Ralph Boehme <slow at samba.org>
---
 source3/modules/vfs_fruit.c | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/source3/modules/vfs_fruit.c b/source3/modules/vfs_fruit.c
index 5a7967490b7..f1f3438acb9 100644
--- a/source3/modules/vfs_fruit.c
+++ b/source3/modules/vfs_fruit.c
@@ -473,6 +473,9 @@ struct fio {
 
 	/* Denote stream type, meta or rsrc */
 	adouble_type_t type;
+
+	/* Whether the create created the stream */
+	bool created;
 };
 
 /*
@@ -5728,6 +5731,7 @@ static NTSTATUS fruit_create_file(vfs_handle_struct *handle,
 	NTSTATUS status;
 	struct fruit_config_data *config = NULL;
 	files_struct *fsp = NULL;
+	struct fio *fio = NULL;
 
 	status = check_aapl(handle, req, in_context_blobs, out_context_blobs);
 	if (!NT_STATUS_IS_OK(status)) {
@@ -5778,6 +5782,11 @@ static NTSTATUS fruit_create_file(vfs_handle_struct *handle,
 		goto fail;
 	}
 
+	fio = (struct fio *)VFS_FETCH_FSP_EXTENSION(handle, fsp);
+	if (fio != NULL && pinfo != NULL && *pinfo == FILE_WAS_CREATED) {
+		fio->created = true;
+	}
+
 	if (is_ntfs_stream_smb_fname(smb_fname)
 	    || fsp->is_directory) {
 		return status;
-- 
2.17.2


From 8d8b0a53654c5bbbd571d6fe82379984a5b4d8a8 Mon Sep 17 00:00:00 2001
From: Ralph Boehme <slow at samba.org>
Date: Wed, 22 Aug 2018 15:22:57 +0200
Subject: [PATCH 27/35] vfs_fruit: prepare struct fio for fake-fd and on-demand
 opening

Not used for now, that comes in the subsequent commits.

Bug: https://bugzilla.samba.org/show_bug.cgi?id=13646

Signed-off-by: Ralph Boehme <slow at samba.org>
---
 source3/modules/vfs_fruit.c | 11 +++++++++++
 1 file changed, 11 insertions(+)

diff --git a/source3/modules/vfs_fruit.c b/source3/modules/vfs_fruit.c
index f1f3438acb9..993fd788c23 100644
--- a/source3/modules/vfs_fruit.c
+++ b/source3/modules/vfs_fruit.c
@@ -476,6 +476,17 @@ struct fio {
 
 	/* Whether the create created the stream */
 	bool created;
+
+	/*
+	 * AFP_AfpInfo stream created, but not written yet, thus still a fake
+	 * pipe fd. This is set to true in fruit_open_meta if there was no
+	 * exisiting stream but the caller requested O_CREAT. It is later set to
+	 * false when we get a write on the stream that then does open and
+	 * create the stream.
+	 */
+	bool fake_fd;
+	int flags;
+	int mode;
 };
 
 /*
-- 
2.17.2


From e4ba9f90fbcef89354d27948b201a898ae21554a Mon Sep 17 00:00:00 2001
From: Ralph Boehme <slow at samba.org>
Date: Wed, 22 Aug 2018 15:21:08 +0200
Subject: [PATCH 28/35] vfs_fruit: prepare fruit_pwrite_meta() for on-demand
 opening and writing

This avoid creating files or blobs in our streams backend when a client
creates a stream but hasn't written anything yet. This is the only sane
way to implement the following semantics:

* client 1: create stream "file:foo"

* client 2: open stream "file:foo"

The second operation of client 2 must fail with NT_STATUS_NOT_FOUND.

Bug: https://bugzilla.samba.org/show_bug.cgi?id=13646

Signed-off-by: Ralph Boehme <slow at samba.org>
---
 source3/modules/vfs_fruit.c | 34 ++++++++++++++++++++++++++++++++++
 1 file changed, 34 insertions(+)

diff --git a/source3/modules/vfs_fruit.c b/source3/modules/vfs_fruit.c
index 993fd788c23..b7c1709532a 100644
--- a/source3/modules/vfs_fruit.c
+++ b/source3/modules/vfs_fruit.c
@@ -4338,10 +4338,44 @@ static ssize_t fruit_pwrite_meta_stream(vfs_handle_struct *handle,
 					files_struct *fsp, const void *data,
 					size_t n, off_t offset)
 {
+	struct fio *fio = (struct fio *)VFS_FETCH_FSP_EXTENSION(handle, fsp);
 	AfpInfo *ai = NULL;
 	size_t nwritten;
+	int ret;
 	bool ok;
 
+	DBG_DEBUG("Path [%s] offset=%"PRIdMAX", size=%zd\n",
+		  fsp_str_dbg(fsp), (intmax_t)offset, n);
+
+	if (fio == NULL) {
+		return -1;
+	}
+
+	if (fio->fake_fd) {
+		int fd;
+
+		ret = SMB_VFS_NEXT_CLOSE(handle, fsp);
+		if (ret != 0) {
+			DBG_ERR("Close [%s] failed: %s\n",
+				fsp_str_dbg(fsp), strerror(errno));
+			fsp->fh->fd = -1;
+			return -1;
+		}
+
+		fd = SMB_VFS_NEXT_OPEN(handle,
+				       fsp->fsp_name,
+				       fsp,
+				       fio->flags,
+				       fio->mode);
+		if (fd == -1) {
+			DBG_ERR("On-demand create [%s] in write failed: %s\n",
+				fsp_str_dbg(fsp), strerror(errno));
+			return -1;
+		}
+		fsp->fh->fd = fd;
+		fio->fake_fd = false;
+	}
+
 	ai = afpinfo_unpack(talloc_tos(), data);
 	if (ai == NULL) {
 		return -1;
-- 
2.17.2


From 4c7dadd74146b8dbbfdd5da07756381dfe560386 Mon Sep 17 00:00:00 2001
From: Ralph Boehme <slow at samba.org>
Date: Wed, 22 Aug 2018 15:22:08 +0200
Subject: [PATCH 29/35] vfs_fruit: prepare fruit_pread_meta() for reading on
 fake-fd

If the read on the stream fails we may have hit a handle on a just
created stream (fio->created=true) with no data written yet.

If that's the case return an empty initialized FinderInfo blob.

Bug: https://bugzilla.samba.org/show_bug.cgi?id=13646

Signed-off-by: Ralph Boehme <slow at samba.org>
---
 source3/modules/vfs_fruit.c | 22 ++++++++++++++++++++--
 1 file changed, 20 insertions(+), 2 deletions(-)

diff --git a/source3/modules/vfs_fruit.c b/source3/modules/vfs_fruit.c
index b7c1709532a..f91b7b8ff63 100644
--- a/source3/modules/vfs_fruit.c
+++ b/source3/modules/vfs_fruit.c
@@ -4037,8 +4037,7 @@ static ssize_t fruit_pread_meta_stream(vfs_handle_struct *handle,
 	int ret;
 
 	nread = SMB_VFS_NEXT_PREAD(handle, fsp, data, n, offset);
-
-	if (nread == n) {
+	if (nread == -1 || nread == n) {
 		return nread;
 	}
 
@@ -4142,6 +4141,25 @@ static ssize_t fruit_pread_meta(vfs_handle_struct *handle,
 		return -1;
 	}
 
+	if (nread == -1 && fio->created) {
+		AfpInfo *ai = NULL;
+		char afpinfo_buf[AFP_INFO_SIZE];
+
+		ai = afpinfo_new(talloc_tos());
+		if (ai == NULL) {
+			return -1;
+		}
+
+		nread = afpinfo_pack(ai, afpinfo_buf);
+		TALLOC_FREE(ai);
+		if (nread != AFP_INFO_SIZE) {
+			return -1;
+		}
+
+		memcpy(data, afpinfo_buf, to_return);
+		return nread;
+	}
+
 	return nread;
 }
 
-- 
2.17.2


From 05a396fa5aeaafa98975924de5a4102d1edc7055 Mon Sep 17 00:00:00 2001
From: Ralph Boehme <slow at samba.org>
Date: Wed, 22 Aug 2018 16:49:23 +0200
Subject: [PATCH 30/35] vfs_fruit: do ino calculation

As we'll start returning fake fds in open shortly, we can't rely on the
next module to calculat correct inode numbers for streams and must take
over that responsibility.

Bug: https://bugzilla.samba.org/show_bug.cgi?id=13646

Signed-off-by: Ralph Boehme <slow at samba.org>
---
 source3/modules/vfs_fruit.c | 50 +++++++++++++++++++++++++++++++++++--
 1 file changed, 48 insertions(+), 2 deletions(-)

diff --git a/source3/modules/vfs_fruit.c b/source3/modules/vfs_fruit.c
index f91b7b8ff63..0d7868a04c8 100644
--- a/source3/modules/vfs_fruit.c
+++ b/source3/modules/vfs_fruit.c
@@ -4741,6 +4741,14 @@ static int fruit_stat_meta_stream(vfs_handle_struct *handle,
 				  bool follow_links)
 {
 	int ret;
+	ino_t ino;
+
+	ret = fruit_stat_base(handle, smb_fname, false);
+	if (ret != 0) {
+		return -1;
+	}
+
+	ino = fruit_inode(&smb_fname->st, smb_fname->stream_name);
 
 	if (follow_links) {
 		ret = SMB_VFS_NEXT_STAT(handle, smb_fname);
@@ -4748,6 +4756,8 @@ static int fruit_stat_meta_stream(vfs_handle_struct *handle,
 		ret = SMB_VFS_NEXT_LSTAT(handle, smb_fname);
 	}
 
+	smb_fname->st.st_ex_ino = ino;
+
 	return ret;
 }
 
@@ -5001,7 +5011,41 @@ static int fruit_fstat_meta_stream(vfs_handle_struct *handle,
 				   files_struct *fsp,
 				   SMB_STRUCT_STAT *sbuf)
 {
-	return SMB_VFS_NEXT_FSTAT(handle, fsp, sbuf);
+	struct fio *fio = (struct fio *)VFS_FETCH_FSP_EXTENSION(handle, fsp);
+	ino_t ino;
+	int ret;
+
+	if (fio == NULL) {
+		return -1;
+	}
+
+	if (fio->fake_fd) {
+		ret = fruit_stat_base(handle, fsp->base_fsp->fsp_name, false);
+		if (ret != 0) {
+			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;
+	}
+
+	ret = fruit_stat_base(handle, fsp->base_fsp->fsp_name, false);
+	if (ret != 0) {
+		return -1;
+	}
+	*sbuf = fsp->base_fsp->fsp_name->st;
+
+	ino = fruit_inode(sbuf, fsp->fsp_name->stream_name);
+
+	ret = SMB_VFS_NEXT_FSTAT(handle, fsp, sbuf);
+	if (ret != 0) {
+		return -1;
+	}
+
+	sbuf->st_ex_ino = ino;
+	return 0;
 }
 
 static int fruit_fstat_meta_netatalk(vfs_handle_struct *handle,
@@ -5236,12 +5280,14 @@ static NTSTATUS fruit_streaminfo_meta_stream(
 		goto out;
 	}
 
-	ret = SMB_VFS_NEXT_STAT(handle, sname);
+	ret = fruit_stat_base(handle, sname, false);
 	if (ret != 0) {
 		status = map_nt_error_from_unix(errno);
 		goto out;
 	}
 
+	sname->st.st_ex_ino = fruit_inode(&sname->st, AFPINFO_STREAM);
+
 	id = SMB_VFS_NEXT_FILE_ID_CREATE(handle, &sname->st);
 
 	lck = get_existing_share_mode_lock(talloc_tos(), id);
-- 
2.17.2


From efc934cd7c4f1c0017a6ffdc43d1cf6398589773 Mon Sep 17 00:00:00 2001
From: Ralph Boehme <slow at samba.org>
Date: Wed, 17 Oct 2018 16:51:34 +0200
Subject: [PATCH 31/35] vfs_fruit: let fruit handle all aio on the FinderInfo
 metadata stream

This will be required to support using fake fds for the FinderInfo
metadata stream.

Bug: https://bugzilla.samba.org/show_bug.cgi?id=13646

Signed-off-by: Ralph Boehme <slow at samba.org>
---
 source3/modules/vfs_fruit.c | 4 +---
 1 file changed, 1 insertion(+), 3 deletions(-)

diff --git a/source3/modules/vfs_fruit.c b/source3/modules/vfs_fruit.c
index 0d7868a04c8..380823ca334 100644
--- a/source3/modules/vfs_fruit.c
+++ b/source3/modules/vfs_fruit.c
@@ -4259,9 +4259,7 @@ static bool fruit_must_handle_aio_stream(struct fio *fio)
 		return false;
 	};
 
-	if ((fio->type == ADOUBLE_META) &&
-	    (fio->config->meta == FRUIT_META_NETATALK))
-	{
+	if (fio->type == ADOUBLE_META) {
 		return true;
 	}
 
-- 
2.17.2


From f9c53873dbc0c8aaf6674f389c3905ea23747efe Mon Sep 17 00:00:00 2001
From: Ralph Boehme <slow at samba.org>
Date: Sat, 20 Oct 2018 23:46:43 +0200
Subject: [PATCH 32/35] vfs_fruit: pass stream size to
 delete_invalid_meta_stream()

delete_invalid_meta_stream() is meant to guard against random data being
present in the FinderInfo stream. If the stream size is 0, it's likely a
freshly created stream where no data has been written to yet, so don't
delete it.

Bug: https://bugzilla.samba.org/show_bug.cgi?id=13646

Signed-off-by: Ralph Boehme <slow at samba.org>
---
 source3/modules/vfs_fruit.c | 15 ++++++++++++---
 1 file changed, 12 insertions(+), 3 deletions(-)

diff --git a/source3/modules/vfs_fruit.c b/source3/modules/vfs_fruit.c
index 380823ca334..b19eca98691 100644
--- a/source3/modules/vfs_fruit.c
+++ b/source3/modules/vfs_fruit.c
@@ -5195,7 +5195,8 @@ static NTSTATUS delete_invalid_meta_stream(
 	const struct smb_filename *smb_fname,
 	TALLOC_CTX *mem_ctx,
 	unsigned int *pnum_streams,
-	struct stream_struct **pstreams)
+	struct stream_struct **pstreams,
+	off_t size)
 {
 	struct smb_filename *sname = NULL;
 	int ret;
@@ -5206,6 +5207,10 @@ static NTSTATUS delete_invalid_meta_stream(
 		return NT_STATUS_INTERNAL_ERROR;
 	}
 
+	if (size == 0) {
+		return NT_STATUS_OK;
+	}
+
 	sname = synthetic_smb_fname(talloc_tos(),
 				    smb_fname->base_name,
 				    AFPINFO_STREAM_NAME,
@@ -5259,8 +5264,12 @@ static NTSTATUS fruit_streaminfo_meta_stream(
 		DBG_ERR("Removing invalid AFPINFO_STREAM size [%jd] from [%s]\n",
 			(intmax_t)stream[i].size, smb_fname_str_dbg(smb_fname));
 
-		return delete_invalid_meta_stream(handle, smb_fname, mem_ctx,
-						  pnum_streams, pstreams);
+		return delete_invalid_meta_stream(handle,
+						  smb_fname,
+						  mem_ctx,
+						  pnum_streams,
+						  pstreams,
+						  stream[i].size);
 	}
 
 	/*
-- 
2.17.2


From ae33a7168ebcc92de64d873214f7e818ae28207d Mon Sep 17 00:00:00 2001
From: Ralph Boehme <slow at samba.org>
Date: Sat, 20 Oct 2018 23:40:14 +0200
Subject: [PATCH 33/35] vfs_fruit: let fruit_pwrite_meta_stream also ftruncate
 empty FinderInfo

fruit_streaminfo currently filters out the FinderInfo stream is
delete-on-close is set. We set it here internally, but the client may
also set it over SMB. Turns out that the macOS SMB server does NOT
filter out FinderInfo stream with delete-on-close set, so we must change
the way filtering is done in fruit_streaminfo.

Filtering is now done based on the FinderInfo stream being 0-bytes large which
is why I'm adding the ftruncate here.

No idea why the tests that check the filtering passed the commits
leading up to this one, but if you revert this commit after applying the
whole patchset, the "delete AFP_AfpInfo by writing all 0" test will fail.

Bug: https://bugzilla.samba.org/show_bug.cgi?id=13646

Signed-off-by: Ralph Boehme <slow at samba.org>
---
 source3/modules/vfs_fruit.c | 30 ++++++++++++++++++------------
 1 file changed, 18 insertions(+), 12 deletions(-)

diff --git a/source3/modules/vfs_fruit.c b/source3/modules/vfs_fruit.c
index b19eca98691..5bcffca82fc 100644
--- a/source3/modules/vfs_fruit.c
+++ b/source3/modules/vfs_fruit.c
@@ -4397,23 +4397,29 @@ static ssize_t fruit_pwrite_meta_stream(vfs_handle_struct *handle,
 		return -1;
 	}
 
-	nwritten = SMB_VFS_NEXT_PWRITE(handle, fsp, data, n, offset);
-	if (nwritten != n) {
-		return -1;
-	}
-
-	if (!ai_empty_finderinfo(ai)) {
-		return n;
-	}
+	if (ai_empty_finderinfo(ai)) {
+		ret = SMB_VFS_NEXT_FTRUNCATE(handle, fsp, 0);
+		if (ret != 0) {
+			DBG_ERR("SMB_VFS_NEXT_FTRUNCATE on [%s] failed\n",
+				fsp_str_dbg(fsp));
+			return -1;
+		}
 
-	ok = set_delete_on_close(
+		ok = set_delete_on_close(
 			fsp,
 			true,
 			handle->conn->session_info->security_token,
 			handle->conn->session_info->unix_token);
-	if (!ok) {
-		DBG_ERR("set_delete_on_close on [%s] failed\n",
-			fsp_str_dbg(fsp));
+		if (!ok) {
+			DBG_ERR("set_delete_on_close on [%s] failed\n",
+				fsp_str_dbg(fsp));
+			return -1;
+		}
+		return n;
+	}
+
+	nwritten = SMB_VFS_NEXT_PWRITE(handle, fsp, data, n, offset);
+	if (nwritten != n) {
 		return -1;
 	}
 
-- 
2.17.2


From dcc10d7d5ea3bd72bb8aa82bea13ea368d034236 Mon Sep 17 00:00:00 2001
From: Ralph Boehme <slow at samba.org>
Date: Sat, 20 Oct 2018 23:50:32 +0200
Subject: [PATCH 34/35] vfs_fruit: don't check for delete-on-close on the
 FinderInfo stream

macOS SMB server doesn't filter out the FinderInfo stream if it has
delete-on-close set.

Bug: https://bugzilla.samba.org/show_bug.cgi?id=13646

Signed-off-by: Ralph Boehme <slow at samba.org>
---
 source3/modules/vfs_fruit.c | 73 +------------------------------------
 1 file changed, 1 insertion(+), 72 deletions(-)

diff --git a/source3/modules/vfs_fruit.c b/source3/modules/vfs_fruit.c
index 5bcffca82fc..ed268ebe1ef 100644
--- a/source3/modules/vfs_fruit.c
+++ b/source3/modules/vfs_fruit.c
@@ -5245,16 +5245,7 @@ static NTSTATUS fruit_streaminfo_meta_stream(
 {
 	struct stream_struct *stream = *pstreams;
 	unsigned int num_streams = *pnum_streams;
-	struct smb_filename *sname = NULL;
-	char *full_name = NULL;
-	uint32_t name_hash;
-	struct share_mode_lock *lck = NULL;
-	struct file_id id = {0};
-	bool delete_on_close_set;
 	int i;
-	int ret;
-	NTSTATUS status;
-	bool ok;
 
 	for (i = 0; i < num_streams; i++) {
 		if (strequal_m(stream[i].name, AFPINFO_STREAM)) {
@@ -5278,70 +5269,8 @@ static NTSTATUS fruit_streaminfo_meta_stream(
 						  stream[i].size);
 	}
 
-	/*
-	 * Now check if there's a delete-on-close pending on the stream. If so,
-	 * hide the stream. This behaviour was verified against a macOS 10.12
-	 * SMB server.
-	 */
 
-	sname = synthetic_smb_fname(talloc_tos(),
-				    smb_fname->base_name,
-				    AFPINFO_STREAM_NAME,
-				    NULL, 0);
-	if (sname == NULL) {
-		status = NT_STATUS_NO_MEMORY;
-		goto out;
-	}
-
-	ret = fruit_stat_base(handle, sname, false);
-	if (ret != 0) {
-		status = map_nt_error_from_unix(errno);
-		goto out;
-	}
-
-	sname->st.st_ex_ino = fruit_inode(&sname->st, AFPINFO_STREAM);
-
-	id = SMB_VFS_NEXT_FILE_ID_CREATE(handle, &sname->st);
-
-	lck = get_existing_share_mode_lock(talloc_tos(), id);
-	if (lck == NULL) {
-		status = NT_STATUS_OK;
-		goto out;
-	}
-
-	full_name = talloc_asprintf(talloc_tos(),
-				    "%s%s",
-				    sname->base_name,
-				    AFPINFO_STREAM);
-	if (full_name == NULL) {
-		status = NT_STATUS_NO_MEMORY;
-		goto out;
-	}
-
-	status = file_name_hash(handle->conn, full_name, &name_hash);
-	if (!NT_STATUS_IS_OK(status)) {
-		goto out;
-	}
-
-	delete_on_close_set = is_delete_on_close_set(lck, name_hash);
-	if (delete_on_close_set) {
-		ok = del_fruit_stream(mem_ctx,
-				      pnum_streams,
-				      pstreams,
-				      AFPINFO_STREAM);
-		if (!ok) {
-			status = NT_STATUS_INTERNAL_ERROR;
-			goto out;
-		}
-	}
-
-	status  = NT_STATUS_OK;
-
-out:
-	TALLOC_FREE(sname);
-	TALLOC_FREE(lck);
-	TALLOC_FREE(full_name);
-	return status;
+	return NT_STATUS_OK;
 }
 
 static NTSTATUS fruit_streaminfo_meta_netatalk(
-- 
2.17.2


From dcd6d880aa552ea73db8b23c45d0029acd9f304a Mon Sep 17 00:00:00 2001
From: Ralph Boehme <slow at samba.org>
Date: Wed, 22 Aug 2018 15:25:26 +0200
Subject: [PATCH 35/35] vfs_fruit: let fruit_open_meta() with O_CREAT return a
 fake-fd

This is the final step in implementing the needed macOS semantics on the
FinderInfo stream: as long as the client hasn't written a non-zero
FinderInfo blob to the stream, there mustn't be a visible filesystem
entry for other openers.

Bug: https://bugzilla.samba.org/show_bug.cgi?id=13646

Signed-off-by: Ralph Boehme <slow at samba.org>
---
 selftest/knownfail.d/samba3.vfs.fruit |   3 -
 source3/modules/vfs_fruit.c           | 165 +++++++++++---------------
 2 files changed, 72 insertions(+), 96 deletions(-)

diff --git a/selftest/knownfail.d/samba3.vfs.fruit b/selftest/knownfail.d/samba3.vfs.fruit
index e44aff6d4b0..d401b2d9a88 100644
--- a/selftest/knownfail.d/samba3.vfs.fruit
+++ b/selftest/knownfail.d/samba3.vfs.fruit
@@ -1,6 +1,3 @@
 ^samba3.vfs.fruit streams_depot.OS X AppleDouble file conversion\(nt4_dc\)
 ^samba3.vfs.fruit streams_depot.OS X AppleDouble file conversion without embedded xattr\(nt4_dc\)
 ^samba3.vfs.fruit_netatalk.locking conflict\(nt4_dc\)
-^samba3.vfs.fruit metadata_netatalk.empty_stream\(nt4_dc\)
-^samba3.vfs.fruit metadata_stream.empty_stream\(nt4_dc\)
-^samba3.vfs.fruit streams_depot.empty_stream\(nt4_dc\)
diff --git a/source3/modules/vfs_fruit.c b/source3/modules/vfs_fruit.c
index ed268ebe1ef..534900e6d67 100644
--- a/source3/modules/vfs_fruit.c
+++ b/source3/modules/vfs_fruit.c
@@ -3246,66 +3246,68 @@ static int fruit_connect(vfs_handle_struct *handle,
 	return rc;
 }
 
+static int fruit_fake_fd(void)
+{
+	int pipe_fds[2];
+	int fd;
+	int ret;
+
+	/*
+	 * Return a valid fd, but ensure any attempt to use it returns
+	 * an error (EPIPE). Once we get a write on the handle, we open
+	 * the real fd.
+	 */
+	ret = pipe(pipe_fds);
+	if (ret != 0) {
+		return -1;
+	}
+	fd = pipe_fds[0];
+	close(pipe_fds[1]);
+
+	return fd;
+}
+
 static int fruit_open_meta_stream(vfs_handle_struct *handle,
 				  struct smb_filename *smb_fname,
 				  files_struct *fsp,
 				  int flags,
 				  mode_t mode)
 {
-	AfpInfo *ai = NULL;
-	char afpinfo_buf[AFP_INFO_SIZE];
-	ssize_t len, written;
-	int hostfd = -1;
-	int rc = -1;
+	struct fruit_config_data *config = NULL;
+	struct fio *fio = NULL;
+	int open_flags = flags & ~O_CREAT;
+	int fd;
 
-	hostfd = SMB_VFS_NEXT_OPEN(handle, smb_fname, fsp, flags, mode);
-	if (hostfd == -1) {
-		return -1;
-	}
+	DBG_DEBUG("Path [%s]\n", smb_fname_str_dbg(smb_fname));
 
-	if (!(flags & (O_CREAT | O_TRUNC))) {
-		return hostfd;
-	}
+	SMB_VFS_HANDLE_GET_DATA(handle, config,
+				struct fruit_config_data, return -1);
 
-	ai = afpinfo_new(talloc_tos());
-	if (ai == NULL) {
-		rc = -1;
-		goto fail;
-	}
+	fio = VFS_ADD_FSP_EXTENSION(handle, fsp, struct fio, NULL);
+	fio->type = ADOUBLE_META;
+	fio->config = config;
 
-	len = afpinfo_pack(ai, afpinfo_buf);
-	if (len != AFP_INFO_SIZE) {
-		rc = -1;
-		goto fail;
+	fd = SMB_VFS_NEXT_OPEN(handle, smb_fname, fsp, open_flags, mode);
+	if (fd != -1) {
+		return fd;
 	}
 
-	/* Set fd, needed in SMB_VFS_NEXT_PWRITE() */
-	fsp->fh->fd = hostfd;
-
-	written = SMB_VFS_NEXT_PWRITE(handle, fsp, afpinfo_buf,
-				      AFP_INFO_SIZE, 0);
-	fsp->fh->fd = -1;
-	if (written != AFP_INFO_SIZE) {
-		DBG_ERR("bad write [%zd/%d]\n", written, AFP_INFO_SIZE);
-		rc = -1;
-		goto fail;
+	if (!(flags & O_CREAT)) {
+		VFS_REMOVE_FSP_EXTENSION(handle, fsp);
+		return -1;
 	}
 
-	rc = 0;
+	fd = fruit_fake_fd();
+	if (fd == -1) {
+		VFS_REMOVE_FSP_EXTENSION(handle, fsp);
+		return -1;
+	}
 
-fail:
-	DBG_DEBUG("rc=%d, fd=%d\n", rc, hostfd);
+	fio->fake_fd = true;
+	fio->flags = flags;
+	fio->mode = mode;
 
-	if (rc != 0) {
-		int saved_errno = errno;
-		if (hostfd >= 0) {
-			fsp->fh->fd = hostfd;
-			SMB_VFS_NEXT_CLOSE(handle, fsp);
-		}
-		hostfd = -1;
-		errno = saved_errno;
-	}
-	return hostfd;
+	return fd;
 }
 
 static int fruit_open_meta_netatalk(vfs_handle_struct *handle,
@@ -3314,56 +3316,42 @@ static int fruit_open_meta_netatalk(vfs_handle_struct *handle,
 				    int flags,
 				    mode_t mode)
 {
-	int rc;
-	int fakefd = -1;
+	struct fruit_config_data *config = NULL;
+	struct fio *fio = NULL;
 	struct adouble *ad = NULL;
-	int fds[2];
+	bool meta_exists = false;
+	int fd;
 
 	DBG_DEBUG("Path [%s]\n", smb_fname_str_dbg(smb_fname));
 
-	/*
-	 * Return a valid fd, but ensure any attempt to use it returns an error
-	 * (EPIPE). All operations on the smb_fname or the fsp will use path
-	 * based syscalls.
-	 */
-	rc = pipe(fds);
-	if (rc != 0) {
-		goto exit;
+	ad = ad_get(talloc_tos(), handle, smb_fname, ADOUBLE_META);
+	if (ad != NULL) {
+		meta_exists = true;
 	}
-	fakefd = fds[0];
-	close(fds[1]);
-
-	if (flags & (O_CREAT | O_TRUNC)) {
-		/*
-		 * The attribute does not exist or needs to be truncated,
-		 * create an AppleDouble EA
-		 */
-		ad = ad_init(fsp, handle, ADOUBLE_META);
-		if (ad == NULL) {
-			rc = -1;
-			goto exit;
-		}
 
-		rc = ad_set(ad, fsp->fsp_name);
-		if (rc != 0) {
-			rc = -1;
-			goto exit;
-		}
+	TALLOC_FREE(ad);
 
-		TALLOC_FREE(ad);
+	if (!meta_exists && !(flags & O_CREAT)) {
+		errno = ENOENT;
+		return -1;
 	}
 
-exit:
-	DEBUG(10, ("fruit_open meta rc=%d, fd=%d\n", rc, fakefd));
-	if (rc != 0) {
-		int saved_errno = errno;
-		if (fakefd >= 0) {
-			close(fakefd);
-		}
-		fakefd = -1;
-		errno = saved_errno;
+	fd = fruit_fake_fd();
+	if (fd == -1) {
+		return -1;
 	}
-	return fakefd;
+
+	SMB_VFS_HANDLE_GET_DATA(handle, config,
+				struct fruit_config_data, return -1);
+
+	fio = VFS_ADD_FSP_EXTENSION(handle, fsp, struct fio, NULL);
+	fio->type = ADOUBLE_META;
+	fio->config = config;
+	fio->fake_fd = true;
+	fio->flags = flags;
+	fio->mode = mode;
+
+	return fd;
 }
 
 static int fruit_open_meta(vfs_handle_struct *handle,
@@ -3372,7 +3360,6 @@ static int fruit_open_meta(vfs_handle_struct *handle,
 {
 	int fd;
 	struct fruit_config_data *config = NULL;
-	struct fio *fio = NULL;
 
 	DBG_DEBUG("path [%s]\n", smb_fname_str_dbg(smb_fname));
 
@@ -3397,14 +3384,6 @@ static int fruit_open_meta(vfs_handle_struct *handle,
 
 	DBG_DEBUG("path [%s] fd [%d]\n", smb_fname_str_dbg(smb_fname), fd);
 
-	if (fd == -1) {
-		return -1;
-	}
-
-	fio = VFS_ADD_FSP_EXTENSION(handle, fsp, struct fio, NULL);
-	fio->type = ADOUBLE_META;
-	fio->config = config;
-
 	return fd;
 }
 
-- 
2.17.2



More information about the samba-technical mailing list