[PATCH] Add a somewhat hackish option to limit the size of TimeMachine backups

Ralph Böhme slow at samba.org
Fri Jan 5 10:57:42 UTC 2018


On Thu, Jan 04, 2018 at 05:08:29PM -0800, Jeremy Allison wrote:
> On Wed, Jan 03, 2018 at 11:04:30AM +0100, Ralph Böhme wrote:
> > 
> > The good thing: it has a test! :)
> > 
> > Please review & push if happy. Thanks!
> 
> Looks pretty good.
> 
> There are just a couple of comments inline. Can you take a look
> and get back to me ?
> 
> > +	uint64_t uint64;
> 
>                  ^^^^^^
>                  'uint64' is a *horrible* confusing name
> for a variable. It looks like a type definition.
> Please change it to something meaningful.

Fixed.

> > +	*dfree = *dsize - (state.total_size / 512);
>                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>  Can this go negative ? What would
>  happen if it did ?

You bet it can! Thanks for catching this. Fixed.

I also noticed that the test failed in make test. I didn't notice this as I kept
running it directly from smbtorture against a local server and share where it
worked. While at it, I moved the test to it's own test-suite so I can run it
against a dedicated new share that has the necessary options set up.

Thanks!
-slow

-- 
Ralph Boehme, Samba Team       https://samba.org/
Samba Developer, SerNet GmbH   https://sernet.de/en/samba/
-------------- next part --------------
From 9723a39d055db4ceb5ad181f499dd3cf5eb11b5c Mon Sep 17 00:00:00 2001
From: Ralph Boehme <slow at samba.org>
Date: Fri, 3 Nov 2017 10:56:29 +0100
Subject: [PATCH 1/2] vfs_fruit: add "time machine max size" option

This can be used to configure a per client filesystem size limit on
TimeMachine shares.

It's a nasty hack but it was reportedly working well in Netatalk where
it's taken from.

Signed-off-by: Ralph Boehme <slow at samba.org>
---
 docs-xml/manpages/vfs_fruit.8.xml |  18 ++
 source3/modules/vfs_fruit.c       | 428 ++++++++++++++++++++++++++++++++++++++
 2 files changed, 446 insertions(+)

diff --git a/docs-xml/manpages/vfs_fruit.8.xml b/docs-xml/manpages/vfs_fruit.8.xml
index fcaf1737ce8..7f6a0e7c022 100644
--- a/docs-xml/manpages/vfs_fruit.8.xml
+++ b/docs-xml/manpages/vfs_fruit.8.xml
@@ -242,6 +242,24 @@
 	  </varlistentry>
 
 	  <varlistentry>
+	    <term>fruit:time machine max size = SIZE [K|M|G|T|P]</term>
+	    <listitem>
+	      <para>Useful for Time Machine: limits the reported disksize, thus
+	      preventing Time Machine from using the whole real disk space for
+	      backup. The option takes a number plus an optional unit.</para>
+	      <para><emphasis>IMPORTANT</emphasis>: This is an approximated
+	      calculation that only takes into account the contents of Time
+	      Machine sparsebundle images. Therefor you <emphasis>MUST
+	      NOT</emphasis> use this volume to store other content when using
+	      this option, because it would NOT be accounted.</para>
+	      <para>The calculation works by reading the band size from the
+	      Info.plist XML file of the sparsebundle, reading the bands/
+	      directory counting the number of band files, and then multiplying
+	      one with the other.</para>
+	    </listitem>
+	  </varlistentry>
+
+	  <varlistentry>
 	    <term>fruit:metadata = [ stream | netatalk ]</term>
 	    <listitem>
 	      <para>Controls where the OS X metadata stream is stored:</para>
diff --git a/source3/modules/vfs_fruit.c b/source3/modules/vfs_fruit.c
index 67dd571c627..dfc0dccf062 100644
--- a/source3/modules/vfs_fruit.c
+++ b/source3/modules/vfs_fruit.c
@@ -141,6 +141,7 @@ struct fruit_config_data {
 	bool aapl_zero_file_id;
 	const char *model;
 	bool time_machine;
+	size_t time_machine_max_size;
 
 	/*
 	 * Additional options, all enabled by default,
@@ -1885,6 +1886,7 @@ static int init_fruit_config(vfs_handle_struct *handle)
 {
 	struct fruit_config_data *config;
 	int enumval;
+	const char *tm_size_str = NULL;
 
 	config = talloc_zero(handle->conn, struct fruit_config_data);
 	if (!config) {
@@ -1983,6 +1985,14 @@ static int init_fruit_config(vfs_handle_struct *handle)
 	config->model = lp_parm_const_string(
 		-1, FRUIT_PARAM_TYPE_NAME, "model", "MacSamba");
 
+	tm_size_str = lp_parm_const_string(
+		SNUM(handle->conn), FRUIT_PARAM_TYPE_NAME,
+		"time machine max size", NULL);
+	if (tm_size_str != NULL) {
+		config->time_machine_max_size =
+			(size_t)conv_str_size(tm_size_str);
+	}
+
 	SMB_VFS_HANDLE_SET_DATA(handle, config,
 				NULL, struct fruit_config_data,
 				return -1);
@@ -6003,8 +6013,426 @@ static NTSTATUS fruit_offload_write_recv(struct vfs_handle_struct *handle,
 	return NT_STATUS_OK;
 }
 
+static char *fruit_get_bandsize_line(char **lines, int numlines)
+{
+	static regex_t re;
+	static bool re_initialized = false;
+	int i;
+	int ret;
+
+	if (!re_initialized) {
+		ret = regcomp(&re, "^[[:blank:]]*<key>band-size</key>$", 0);
+		if (ret != 0) {
+			return NULL;
+		}
+		re_initialized = true;
+	}
+
+	for (i = 0; i < numlines; i++) {
+		regmatch_t matches[1];
+
+		ret = regexec(&re, lines[i], 1, matches, 0);
+		if (ret == 0) {
+			/*
+			 * Check if the match was on the last line, sa we want
+			 * the subsequent line.
+			 */
+			if (i + 1 == numlines) {
+				return NULL;
+			}
+			return lines[i + 1];
+		}
+		if (ret != REG_NOMATCH) {
+			return NULL;
+		}
+	}
+
+	return NULL;
+}
+
+static bool fruit_get_bandsize_from_line(char *line, size_t *_band_size)
+{
+	static regex_t re;
+	static bool re_initialized = false;
+	regmatch_t matches[2];
+	uint64_t band_size;
+	int ret;
+	bool ok;
+
+	if (!re_initialized) {
+		ret = regcomp(&re,
+			      "^[[:blank:]]*"
+			      "<integer>\\([[:digit:]]*\\)</integer>$",
+			      0);
+		if (ret != 0) {
+			return false;
+		}
+		re_initialized = true;
+	}
+
+	ret = regexec(&re, line, 2, matches, 0);
+	if (ret != 0) {
+		DBG_ERR("regex failed [%s]\n", line);
+		return false;
+	}
+
+	line[matches[1].rm_eo] = '\0';
+
+	ok = conv_str_u64(&line[matches[1].rm_so], &band_size);
+	if (!ok) {
+		return false;
+	}
+	*_band_size = (size_t)band_size;
+	return true;
+}
+
+/*
+ * This reads and parses an Info.plist from a TM sparsebundle looking for the
+ * "band-size" key and value.
+ */
+static bool fruit_get_bandsize(vfs_handle_struct *handle,
+			       const char *dir,
+			       size_t *band_size)
+{
+#define INFO_PLIST_MAX_SIZE 64*1024
+	char *plist = NULL;
+	struct smb_filename *smb_fname = NULL;
+	files_struct *fsp = NULL;
+	uint8_t *file_data = NULL;
+	char **lines = NULL;
+	char *band_size_line = NULL;
+	size_t plist_file_size;
+	ssize_t nread;
+	int numlines;
+	int ret;
+	bool ok = false;
+	NTSTATUS status;
+
+	plist = talloc_asprintf(talloc_tos(),
+				"%s/%s/Info.plist",
+				handle->conn->connectpath,
+				dir);
+	if (plist == NULL) {
+		ok = false;
+		goto out;
+	}
+
+	smb_fname = synthetic_smb_fname(talloc_tos(), plist, NULL, NULL, 0);
+	if (smb_fname == NULL) {
+		ok = false;
+		goto out;
+	}
+
+	ret = SMB_VFS_NEXT_LSTAT(handle, smb_fname);
+	if (ret != 0) {
+		DBG_INFO("Ignoring Sparsebundle without Info.plist [%s]\n", dir);
+		ok = true;
+		goto out;
+	}
+
+	plist_file_size = smb_fname->st.st_ex_size;
+
+	if (plist_file_size > INFO_PLIST_MAX_SIZE) {
+		DBG_INFO("%s is too large, ignoring\n", plist);
+		ok = true;
+		goto out;
+	}
+
+	status = SMB_VFS_NEXT_CREATE_FILE(
+		handle,				/* conn */
+		NULL,				/* req */
+		0,				/* root_dir_fid */
+		smb_fname,			/* fname */
+		FILE_GENERIC_READ,		/* access_mask */
+		FILE_SHARE_READ | FILE_SHARE_WRITE, /* share_access */
+		FILE_OPEN,			/* create_disposition */
+		0,				/* create_options */
+		0,				/* file_attributes */
+		INTERNAL_OPEN_ONLY,		/* oplock_request */
+		NULL,				/* lease */
+		0,				/* allocation_size */
+		0,				/* private_flags */
+		NULL,				/* sd */
+		NULL,				/* ea_list */
+		&fsp,				/* result */
+		NULL,				/* psbuf */
+		NULL, NULL);			/* create context */
+	if (!NT_STATUS_IS_OK(status)) {
+		DBG_INFO("Opening [%s] failed [%s]\n",
+			 smb_fname_str_dbg(smb_fname), nt_errstr(status));
+		ok = false;
+		goto out;
+	}
+
+	file_data = talloc_array(talloc_tos(), uint8_t, plist_file_size);
+	if (file_data == NULL) {
+		ok = false;
+		goto out;
+	}
+
+	nread = SMB_VFS_NEXT_PREAD(handle, fsp, file_data, plist_file_size, 0);
+	if (nread != plist_file_size) {
+		DBG_ERR("Short read on [%s]: %zu/%zd\n",
+			fsp_str_dbg(fsp), nread, plist_file_size);
+		ok = false;
+		goto out;
+
+	}
+
+	status = close_file(NULL, fsp, NORMAL_CLOSE);
+	fsp = NULL;
+	if (!NT_STATUS_IS_OK(status)) {
+		DBG_ERR("close_file failed: %s\n", nt_errstr(status));
+		ok = false;
+		goto out;
+	}
+
+	lines = file_lines_parse((char *)file_data,
+				 plist_file_size,
+				 &numlines,
+				 talloc_tos());
+	if (lines == NULL) {
+		ok = false;
+		goto out;
+	}
+
+	band_size_line = fruit_get_bandsize_line(lines, numlines);
+	if (band_size_line == NULL) {
+		DBG_ERR("Didn't find band-size key in [%s]\n",
+			smb_fname_str_dbg(smb_fname));
+		ok = false;
+		goto out;
+	}
+
+	ok = fruit_get_bandsize_from_line(band_size_line, band_size);
+	if (!ok) {
+		DBG_ERR("fruit_get_bandsize_from_line failed\n");
+		goto out;
+	}
+
+	DBG_DEBUG("Parsed band-size [%zu] for [%s]\n", *band_size, plist);
+
+out:
+	if (fsp != NULL) {
+		status = close_file(NULL, fsp, NORMAL_CLOSE);
+		if (!NT_STATUS_IS_OK(status)) {
+			DBG_ERR("close_file failed: %s\n", nt_errstr(status));
+		}
+		fsp = NULL;
+	}
+	TALLOC_FREE(plist);
+	TALLOC_FREE(smb_fname);
+	TALLOC_FREE(file_data);
+	TALLOC_FREE(lines);
+	return ok;
+}
+
+struct fruit_disk_free_state {
+	size_t total_size;
+};
+
+static bool fruit_get_num_bands(vfs_handle_struct *handle,
+				char *bundle,
+				size_t *_nbands)
+{
+	char *path = NULL;
+	struct smb_filename *bands_dir = NULL;
+	DIR *d = NULL;
+	struct dirent *e = NULL;
+	size_t nbands;
+	int ret;
+
+	path = talloc_asprintf(talloc_tos(),
+			       "%s/%s/bands",
+			       handle->conn->connectpath,
+			       bundle);
+	if (path == NULL) {
+		return false;
+	}
+
+	bands_dir = synthetic_smb_fname(talloc_tos(),
+					path,
+					NULL,
+					NULL,
+					0);
+	TALLOC_FREE(path);
+	if (bands_dir == NULL) {
+		return false;
+	}
+
+	d = SMB_VFS_NEXT_OPENDIR(handle, bands_dir, NULL, 0);
+	if (d == NULL) {
+		TALLOC_FREE(bands_dir);
+		return false;
+	}
+
+	nbands = 0;
+
+	for (e = SMB_VFS_NEXT_READDIR(handle, d, NULL);
+	     e != NULL;
+	     e = SMB_VFS_NEXT_READDIR(handle, d, NULL))
+	{
+		if (ISDOT(e->d_name) || ISDOTDOT(e->d_name)) {
+			continue;
+		}
+		nbands++;
+	}
+
+	ret = SMB_VFS_NEXT_CLOSEDIR(handle, d);
+	if (ret != 0) {
+		TALLOC_FREE(bands_dir);
+		return false;
+	}
+
+	DBG_DEBUG("%zu bands in [%s]\n", nbands, smb_fname_str_dbg(bands_dir));
+
+	TALLOC_FREE(bands_dir);
+
+	*_nbands = nbands;
+	return true;
+}
+
+static bool fruit_tmsize_do_dirent(vfs_handle_struct *handle,
+				   struct fruit_disk_free_state *state,
+				   struct dirent *e)
+{
+	bool ok;
+	char *p = NULL;
+	size_t sparsebundle_strlen = strlen("sparsebundle");
+	size_t bandsize;
+	size_t nbands;
+	double tm_size;
+
+	p = strstr(e->d_name, "sparsebundle");
+	if (p == NULL) {
+		return true;
+	}
+
+	if (p[sparsebundle_strlen] != '\0') {
+		return true;
+	}
+
+	DBG_DEBUG("Processing sparsebundle [%s]\n", e->d_name);
+
+	ok = fruit_get_bandsize(handle, e->d_name, &bandsize);
+	if (!ok) {
+		/*
+		 * Beware of race conditions: this may be an uninitialized
+		 * Info.plist that a client is just creating. We don't want let
+		 * this to trigger complete failure.
+		 */
+		DBG_ERR("Processing sparsebundle [%s] failed\n", e->d_name);
+		return true;
+	}
+
+	ok = fruit_get_num_bands(handle, e->d_name, &nbands);
+	if (!ok) {
+		/*
+		 * Beware of race conditions: this may be a backup sparsebundle
+		 * in an early stage lacking a bands subdirectory. We don't want
+		 * let this to trigger complete failure.
+		 */
+		DBG_ERR("Processing sparsebundle [%s] failed\n", e->d_name);
+		return true;
+	}
+
+	tm_size = bandsize * nbands;
+	if (tm_size > UINT64_MAX) {
+		DBG_ERR("tmsize overflow: bandsize [%zu] nbands [%zu]\n",
+			bandsize, nbands);
+		return false;
+	}
+
+	if (state->total_size + tm_size < state->total_size) {
+		DBG_ERR("tmsize overflow: bandsize [%zu] nbands [%zu]\n",
+			bandsize, nbands);
+		return false;
+	}
+
+	state->total_size += tm_size;
+
+	DBG_DEBUG("[%s] tm_size [%.0f] total_size [%zu]\n",
+		  e->d_name, tm_size, state->total_size);
+
+	return true;
+}
+
+/**
+ * Calculate used size of a TimeMachine volume
+ *
+ * This assumes that the volume is used only for TimeMachine.
+ *
+ * - readdir(basedir of share), then
+ * - for every element that matches regex "^\(.*\)\.sparsebundle$" :
+ * - parse "\1.sparsebundle/Info.plist" and read the band-size XML key
+ * - count band files in "\1.sparsebundle/bands/"
+ * - calculate used size of all bands: band_count * band_size
+ **/
+static uint64_t fruit_disk_free(vfs_handle_struct *handle,
+				const struct smb_filename *smb_fname,
+				uint64_t *_bsize,
+				uint64_t *_dfree,
+				uint64_t *_dsize)
+{
+	struct fruit_config_data *config = NULL;
+	struct fruit_disk_free_state state = {0};
+	DIR *d = NULL;
+	struct dirent *e = NULL;
+	uint64_t dfree;
+	uint64_t dsize;
+	int ret;
+	bool ok;
+
+	SMB_VFS_HANDLE_GET_DATA(handle, config,
+				struct fruit_config_data,
+				return UINT64_MAX);
+
+	if (!config->time_machine ||
+	    config->time_machine_max_size == 0)
+	{
+		return SMB_VFS_NEXT_DISK_FREE(handle,
+					      smb_fname,
+					      _bsize,
+					      _dfree,
+					      _dsize);
+	}
+
+	d = SMB_VFS_NEXT_OPENDIR(handle, smb_fname, NULL, 0);
+	if (d == NULL) {
+		return UINT64_MAX;
+	}
+
+	for (e = SMB_VFS_NEXT_READDIR(handle, d, NULL);
+	     e != NULL;
+	     e = SMB_VFS_NEXT_READDIR(handle, d, NULL))
+	{
+		ok = fruit_tmsize_do_dirent(handle, &state, e);
+		if (!ok) {
+			SMB_VFS_NEXT_CLOSEDIR(handle, d);
+			return UINT64_MAX;
+		}
+	}
+
+	ret = SMB_VFS_NEXT_CLOSEDIR(handle, d);
+	if (ret != 0) {
+		return UINT64_MAX;
+	}
+
+	dsize = config->time_machine_max_size / 512;
+	dfree = dsize - (state.total_size / 512);
+	if (dfree > dsize) {
+		dfree = 0;
+	}
+
+	*_bsize = 512;
+	*_dsize = dsize;
+	*_dfree = dfree;
+	return dfree / 2;
+}
+
 static struct vfs_fn_pointers vfs_fruit_fns = {
 	.connect_fn = fruit_connect,
+	.disk_free_fn = fruit_disk_free,
 
 	/* File operations */
 	.chmod_fn = fruit_chmod,
-- 
2.13.6


From d48a99c5d1f448006fb10e206e1dda2fe6917aee Mon Sep 17 00:00:00 2001
From: Ralph Boehme <slow at samba.org>
Date: Tue, 2 Jan 2018 19:09:04 +0100
Subject: [PATCH 2/2] s4/torture: test vfs_fruit "fruit:time machine max size"
 option

Signed-off-by: Ralph Boehme <slow at samba.org>
---
 selftest/target/Samba3.pm   |   8 ++++
 source3/selftest/tests.py   |   4 +-
 source4/torture/vfs/fruit.c | 102 ++++++++++++++++++++++++++++++++++++++++++++
 source4/torture/vfs/vfs.c   |   1 +
 4 files changed, 114 insertions(+), 1 deletion(-)

diff --git a/selftest/target/Samba3.pm b/selftest/target/Samba3.pm
index c9888c2dca3..1e652d8e3f5 100755
--- a/selftest/target/Samba3.pm
+++ b/selftest/target/Samba3.pm
@@ -1957,6 +1957,14 @@ sub provision($$$$$$$$$)
 	path = $shrdir
 	vfs objects = streams_depot acl_xattr
 
+[vfs_fruit_timemachine]
+	path = $shrdir
+	vfs objects = fruit streams_xattr acl_xattr
+	fruit:resource = file
+	fruit:metadata = stream
+	fruit:time machine = yes
+	fruit:time machine max size = 32K
+
 [badname-tmp]
 	path = $badnames_shrdir
 	guest ok = yes
diff --git a/source3/selftest/tests.py b/source3/selftest/tests.py
index 34de4be230b..8075f78b03f 100755
--- a/source3/selftest/tests.py
+++ b/source3/selftest/tests.py
@@ -395,7 +395,7 @@ nbt = ["nbt.dgram" ]
 
 libsmbclient = ["libsmbclient"]
 
-vfs = ["vfs.fruit", "vfs.acl_xattr", "vfs.fruit_netatalk", "vfs.fruit_file_id"]
+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
 
@@ -500,6 +500,8 @@ for t in tests:
         plansmbtorture4testsuite(t, "nt4_dc", '//$SERVER_IP/vfs_fruit_stream_depot -U$USERNAME%$PASSWORD --option=torture:localdir=$SELFTEST_PREFIX/nt4_dc/share --option=torture:share2=vfs_wo_fruit_stream_depot', 'streams_depot')
     elif t == "vfs.fruit_netatalk":
         plansmbtorture4testsuite(t, "nt4_dc", '//$SERVER_IP/vfs_fruit -U$USERNAME%$PASSWORD --option=torture:localdir=$SELFTEST_PREFIX/nt4_dc/share')
+    elif t == "vfs.fruit_timemachine":
+        plansmbtorture4testsuite(t, "nt4_dc", '//$SERVER_IP/vfs_fruit_timemachine -U$USERNAME%$PASSWORD --option=torture:localdir=$SELFTEST_PREFIX/nt4_dc/share')
     elif t == "vfs.fruit_file_id":
         plansmbtorture4testsuite(t, "nt4_dc", '//$SERVER_IP/vfs_fruit -U$USERNAME%$PASSWORD')
     elif t == "rpc.schannel_anon_setpw":
diff --git a/source4/torture/vfs/fruit.c b/source4/torture/vfs/fruit.c
index 04f04e2cd56..476e9204ca7 100644
--- a/source4/torture/vfs/fruit.c
+++ b/source4/torture/vfs/fruit.c
@@ -4481,3 +4481,105 @@ struct torture_suite *torture_vfs_fruit_file_id(TALLOC_CTX *ctx)
 
 	return suite;
 }
+
+static bool test_timemachine_volsize(struct torture_context *tctx,
+				     struct smb2_tree *tree)
+{
+	TALLOC_CTX *mem_ctx = talloc_new(tctx);
+	struct smb2_handle h = {{0}};
+	union smb_fsinfo fsinfo;
+	NTSTATUS status;
+	bool ok = true;
+	const char *info_plist =
+		"<dict>\n"
+		"        <key>band-size</key>\n"
+		"        <integer>8192</integer>\n"
+		"</dict>\n";
+
+	smb2_deltree(tree, "test.sparsebundle");
+
+	ok = enable_aapl(tctx, tree);
+	torture_assert_goto(tctx, ok, ok, done, "enable_aapl failed");
+
+	status = smb2_util_mkdir(tree, "test.sparsebundle");
+	torture_assert_ntstatus_ok_goto(tctx, status, ok, done,
+					"smb2_util_mkdir\n");
+
+	ok = write_stream(tree, __location__, tctx, mem_ctx,
+			  "test.sparsebundle/Info.plist", NULL,
+			   0, strlen(info_plist), info_plist);
+	torture_assert_goto(tctx, ok, ok, done, "write_stream failed\n");
+
+	status = smb2_util_mkdir(tree, "test.sparsebundle/bands");
+	torture_assert_ntstatus_ok_goto(tctx, status, ok, done,
+					"smb2_util_mkdir\n");
+
+	ok = torture_setup_file(tctx, tree, "test.sparsebundle/bands/1", false);
+	torture_assert_goto(tctx, ok, ok, done, "torture_setup_file failed\n");
+
+	ok = torture_setup_file(tctx, tree, "test.sparsebundle/bands/2", false);
+	torture_assert_goto(tctx, ok, ok, done, "torture_setup_file failed\n");
+
+	status = smb2_util_roothandle(tree, &h);
+	torture_assert_ntstatus_ok(tctx, status, "Unable to create root handle");
+
+	ZERO_STRUCT(fsinfo);
+	fsinfo.generic.level = RAW_QFS_SIZE_INFORMATION;
+	fsinfo.generic.handle = h;
+
+	status = smb2_getinfo_fs(tree, tree, &fsinfo);
+	torture_assert_ntstatus_ok(tctx, status, "smb2_getinfo_fs failed");
+
+	torture_comment(tctx, "sectors_per_unit: %" PRIu32"\n"
+			"bytes_per_sector: %" PRIu32"\n"
+			"total_alloc_units: %" PRIu64"\n"
+			"avail_alloc_units: %" PRIu64"\n",
+			fsinfo.size_info.out.sectors_per_unit,
+			fsinfo.size_info.out.bytes_per_sector,
+			fsinfo.size_info.out.total_alloc_units,
+			fsinfo.size_info.out.avail_alloc_units);
+
+	/*
+	 * Let me explain the numbers:
+	 *
+	 * - the share is set to "fruit:time machine max size = 32K"
+	 * - we've faked a bandsize of 8 K in the Info.plist file
+	 * - we've created two bands files
+	 * - one allocation unit is made of two sectors with 512 B each
+	 * => we've consumed 16 allocation units, there should be 16 free
+	 */
+
+	torture_assert_goto(tctx, fsinfo.size_info.out.sectors_per_unit == 2,
+			    ok, done, "Bad sectors_per_unit");
+
+	torture_assert_goto(tctx, fsinfo.size_info.out.bytes_per_sector == 512,
+			    ok, done, "Bad bytes_per_sector");
+
+	torture_assert_goto(tctx, fsinfo.size_info.out.total_alloc_units == 32,
+			    ok, done, "Bad total_alloc_units");
+
+	torture_assert_goto(tctx, fsinfo.size_info.out.avail_alloc_units == 16,
+			    ok, done, "Bad avail_alloc_units");
+
+done:
+	if (!smb2_util_handle_empty(h)) {
+		smb2_util_close(tree, h);
+	}
+	smb2_deltree(tree, "test.sparsebundle");
+	talloc_free(mem_ctx);
+	return ok;
+}
+
+struct torture_suite *torture_vfs_fruit_timemachine(TALLOC_CTX *ctx)
+{
+	struct torture_suite *suite = torture_suite_create(
+		ctx, "fruit_timemachine");
+
+	suite->description = talloc_strdup(
+		suite, "vfs_fruit tests for TimeMachine");
+
+	torture_suite_add_1smb2_test(suite, "Timemachine-volsize",
+				     test_timemachine_volsize);
+
+	return suite;
+}
diff --git a/source4/torture/vfs/vfs.c b/source4/torture/vfs/vfs.c
index eef1f89a37c..64cb0e3d6c4 100644
--- a/source4/torture/vfs/vfs.c
+++ b/source4/torture/vfs/vfs.c
@@ -112,6 +112,7 @@ NTSTATUS torture_vfs_init(TALLOC_CTX *ctx)
 	torture_suite_add_suite(suite, torture_vfs_fruit_netatalk(suite));
 	torture_suite_add_suite(suite, torture_acl_xattr(suite));
 	torture_suite_add_suite(suite, torture_vfs_fruit_file_id(suite));
+	torture_suite_add_suite(suite, torture_vfs_fruit_timemachine(suite));
 
 	torture_register_suite(ctx, suite);
 
-- 
2.13.6



More information about the samba-technical mailing list