test_sparse_basic.c revision 231200
1231200Smm/*-
2231200Smm * Copyright (c) 2010 Michihiro NAKAJIMA
3231200Smm * All rights reserved.
4231200Smm *
5231200Smm * Redistribution and use in source and binary forms, with or without
6231200Smm * modification, are permitted provided that the following conditions
7231200Smm * are met:
8231200Smm * 1. Redistributions of source code must retain the above copyright
9231200Smm *    notice, this list of conditions and the following disclaimer.
10231200Smm * 2. Redistributions in binary form must reproduce the above copyright
11231200Smm *    notice, this list of conditions and the following disclaimer in the
12231200Smm *    documentation and/or other materials provided with the distribution.
13231200Smm *
14231200Smm * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
15231200Smm * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
16231200Smm * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
17231200Smm * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
18231200Smm * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
19231200Smm * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
20231200Smm * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
21231200Smm * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22231200Smm * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
23231200Smm * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24231200Smm */
25231200Smm#include "test.h"
26231200Smm__FBSDID("$FreeBSD$");
27231200Smm
28231200Smm#ifdef HAVE_SYS_IOCTL_H
29231200Smm#include <sys/ioctl.h>
30231200Smm#endif
31231200Smm#ifdef HAVE_SYS_PARAM_H
32231200Smm#include <sys/param.h>
33231200Smm#endif
34231200Smm#ifdef HAVE_SYS_UTSNAME_H
35231200Smm#include <sys/utsname.h>
36231200Smm#endif
37231200Smm#ifdef HAVE_FCNTL_H
38231200Smm#include <fcntl.h>
39231200Smm#endif
40231200Smm#ifdef HAVE_LIMITS_H
41231200Smm#include <limits.h>
42231200Smm#endif
43231200Smm#ifdef HAVE_UNISTD_H
44231200Smm#include <unistd.h>
45231200Smm#endif
46231200Smm#ifdef HAVE_LINUX_FIEMAP_H
47231200Smm#include <linux/fiemap.h>
48231200Smm#endif
49231200Smm#ifdef HAVE_LINUX_FS_H
50231200Smm#include <linux/fs.h>
51231200Smm#endif
52231200Smm
53231200Smm/*
54231200Smm * NOTE: On FreeBSD and Solaris, this test needs ZFS.
55231200Smm * You may should perfom this test as
56231200Smm * 'TMPDIR=<a directory on the ZFS> libarchive_test'.
57231200Smm */
58231200Smm
59231200Smmstruct sparse {
60231200Smm	enum { DATA, HOLE, END } type;
61231200Smm	size_t	size;
62231200Smm};
63231200Smm
64231200Smmstatic void create_sparse_file(const char *, const struct sparse *);
65231200Smm
66231200Smm#if defined(_WIN32) && !defined(__CYGWIN__)
67231200Smm#include <winioctl.h>
68231200Smm/*
69231200Smm * Create a sparse file on Windows.
70231200Smm */
71231200Smm
72231200Smm#if !defined(PATH_MAX)
73231200Smm#define	PATH_MAX	MAX_PATH
74231200Smm#endif
75231200Smm#if !defined(__BORLANDC__)
76231200Smm#define getcwd _getcwd
77231200Smm#endif
78231200Smm
79231200Smmstatic int
80231200Smmis_sparse_supported(const char *path)
81231200Smm{
82231200Smm	char root[MAX_PATH+1];
83231200Smm	char vol[MAX_PATH+1];
84231200Smm	char sys[MAX_PATH+1];
85231200Smm	DWORD flags;
86231200Smm	BOOL r;
87231200Smm
88231200Smm	strncpy(root, path, sizeof(root)-1);
89231200Smm	if (((root[0] >= 'c' && root[0] <= 'z') ||
90231200Smm	    (root[0] >= 'C' && root[0] <= 'Z')) &&
91231200Smm		root[1] == ':' &&
92231200Smm	    (root[2] == '\\' || root[2] == '/'))
93231200Smm		root[3] = '\0';
94231200Smm	else
95231200Smm		return (0);
96231200Smm	assertEqualInt((r = GetVolumeInformation(root, vol,
97231200Smm	    sizeof(vol), NULL, NULL, &flags, sys, sizeof(sys))), 1);
98231200Smm	return (r != 0 && (flags & FILE_SUPPORTS_SPARSE_FILES) != 0);
99231200Smm}
100231200Smm
101231200Smmstatic void
102231200Smmcreate_sparse_file(const char *path, const struct sparse *s)
103231200Smm{
104231200Smm	char buff[1024];
105231200Smm	HANDLE handle;
106231200Smm	DWORD dmy;
107231200Smm
108231200Smm	memset(buff, ' ', sizeof(buff));
109231200Smm
110231200Smm	handle = CreateFileA(path, GENERIC_WRITE, 0,
111231200Smm	    NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL,
112231200Smm	    NULL);
113231200Smm	assert(handle != INVALID_HANDLE_VALUE);
114231200Smm	assert(DeviceIoControl(handle, FSCTL_SET_SPARSE, NULL, 0,
115231200Smm	    NULL, 0, &dmy, NULL) != 0);
116231200Smm	while (s->type != END) {
117231200Smm		if (s->type == HOLE) {
118231200Smm			LARGE_INTEGER distance;
119231200Smm
120231200Smm			distance.QuadPart = s->size;
121231200Smm			assert(SetFilePointerEx(handle, distance,
122231200Smm			    NULL, FILE_CURRENT) != 0);
123231200Smm		} else {
124231200Smm			DWORD w, wr, size;
125231200Smm
126231200Smm			size = s->size;
127231200Smm			while (size) {
128231200Smm				if (size > sizeof(buff))
129231200Smm					w = sizeof(buff);
130231200Smm				else
131231200Smm					w = size;
132231200Smm				assert(WriteFile(handle, buff, w, &wr, NULL) != 0);
133231200Smm				size -= wr;
134231200Smm			}
135231200Smm		}
136231200Smm		s++;
137231200Smm	}
138231200Smm	assertEqualInt(CloseHandle(handle), 1);
139231200Smm}
140231200Smm
141231200Smm#else
142231200Smm
143231200Smm#if defined(_PC_MIN_HOLE_SIZE)
144231200Smm
145231200Smm/*
146231200Smm * FreeBSD and Solaris can detect 'hole' of a sparse file
147231200Smm * through lseek(HOLE) on ZFS. (UFS does not support yet)
148231200Smm */
149231200Smm
150231200Smmstatic int
151231200Smmis_sparse_supported(const char *path)
152231200Smm{
153231200Smm	return (pathconf(path, _PC_MIN_HOLE_SIZE) > 0);
154231200Smm}
155231200Smm
156231200Smm#elif defined(__linux__)&& defined(HAVE_LINUX_FIEMAP_H)
157231200Smm
158231200Smm/*
159231200Smm * FIEMAP, which can detect 'hole' of a sparse file, has
160231200Smm * been supported from 2.6.28
161231200Smm */
162231200Smm
163231200Smmstatic int
164231200Smmis_sparse_supported(const char *path)
165231200Smm{
166231200Smm	const struct sparse sparse_file[] = {
167231200Smm 		/* This hole size is too small to create a sparse
168231200Smm		 * files for almost filesystem. */
169231200Smm		{ HOLE,	 1024 }, { DATA, 10240 },
170231200Smm		{ END,	0 }
171231200Smm	};
172231200Smm	struct utsname ut;
173231200Smm	char *p, *e;
174231200Smm	long d;
175231200Smm	int fd, r;
176231200Smm	struct fiemap *fm;
177231200Smm	char buff[1024];
178231200Smm	const char *testfile = "can_sparse";
179231200Smm
180231200Smm	memset(&ut, 0, sizeof(ut));
181231200Smm	assertEqualInt(uname(&ut), 0);
182231200Smm	p = ut.release;
183231200Smm	d = strtol(p, &e, 10);
184231200Smm	if (d < 2 || *e != '.')
185231200Smm		return (0);
186231200Smm	if (d == 2) {
187231200Smm		p = e + 1;
188231200Smm		d = strtol(p, &e, 10);
189231200Smm		if (d < 6 || *e != '.')
190231200Smm			return (0);
191231200Smm		p = e + 1;
192231200Smm		d = strtol(p, NULL, 10);
193231200Smm		if (d < 28)
194231200Smm			return (0);
195231200Smm	}
196231200Smm	create_sparse_file(testfile, sparse_file);
197231200Smm	fd = open(testfile,  O_RDWR);
198231200Smm	if (fd < 0)
199231200Smm		return (0);
200231200Smm	fm = (struct fiemap *)buff;
201231200Smm	fm->fm_start = 0;
202231200Smm	fm->fm_length = ~0ULL;;
203231200Smm	fm->fm_flags = FIEMAP_FLAG_SYNC;
204231200Smm	fm->fm_extent_count = (sizeof(buff) - sizeof(*fm))/
205231200Smm		sizeof(struct fiemap_extent);
206231200Smm	r = ioctl(fd, FS_IOC_FIEMAP, fm);
207231200Smm	if (r < 0 && (errno == ENOTTY || errno == EOPNOTSUPP))
208231200Smm		return (0);/* Not supported. */
209231200Smm	close(fd);
210231200Smm	unlink(testfile);
211231200Smm	return (1);
212231200Smm}
213231200Smm
214231200Smm#else
215231200Smm
216231200Smm/*
217231200Smm * Other system may do not have the API such as lseek(HOLE),
218231200Smm * which detect 'hole' of a sparse file.
219231200Smm */
220231200Smm
221231200Smmstatic int
222231200Smmis_sparse_supported(const char *path)
223231200Smm{
224231200Smm	return (0);
225231200Smm}
226231200Smm
227231200Smm#endif
228231200Smm
229231200Smm/*
230231200Smm * Create a sparse file on POSIX like system.
231231200Smm */
232231200Smm
233231200Smmstatic void
234231200Smmcreate_sparse_file(const char *path, const struct sparse *s)
235231200Smm{
236231200Smm	char buff[1024];
237231200Smm	int fd;
238231200Smm
239231200Smm	memset(buff, ' ', sizeof(buff));
240231200Smm	assert((fd = open(path, O_CREAT | O_WRONLY, 0600)) != -1);
241231200Smm	while (s->type != END) {
242231200Smm		if (s->type == HOLE) {
243231200Smm			assert(lseek(fd, s->size, SEEK_CUR) != (off_t)-1);
244231200Smm		} else {
245231200Smm			size_t w, size;
246231200Smm
247231200Smm			size = s->size;
248231200Smm			while (size) {
249231200Smm				if (size > sizeof(buff))
250231200Smm					w = sizeof(buff);
251231200Smm				else
252231200Smm					w = size;
253231200Smm				assert(write(fd, buff, w) != (ssize_t)-1);
254231200Smm				size -= w;
255231200Smm			}
256231200Smm		}
257231200Smm		s++;
258231200Smm	}
259231200Smm	close(fd);
260231200Smm}
261231200Smm
262231200Smm#endif
263231200Smm
264231200Smm/*
265231200Smm * Sparse test with directory traversals.
266231200Smm */
267231200Smmstatic void
268231200Smmverify_sparse_file(struct archive *a, const char *path,
269231200Smm    const struct sparse *sparse, int blocks)
270231200Smm{
271231200Smm	struct archive_entry *ae;
272231200Smm	const void *buff;
273231200Smm	size_t bytes_read;
274231200Smm	int64_t offset;
275231200Smm	int64_t total;
276231200Smm	int data_blocks, hole;
277231200Smm
278231200Smm	create_sparse_file(path, sparse);
279231200Smm	assert((ae = archive_entry_new()) != NULL);
280231200Smm	assertEqualIntA(a, ARCHIVE_OK, archive_read_disk_open(a, path));
281231200Smm	assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header2(a, ae));
282231200Smm	/* Verify the number of holes only, not its offset nor its
283231200Smm	 * length because those alignments are deeply dependence on
284231200Smm	 * its filesystem. */
285231200Smm	assertEqualInt(blocks, archive_entry_sparse_count(ae));
286231200Smm	total = 0;
287231200Smm	data_blocks = 0;
288231200Smm	hole = 0;
289231200Smm	while (ARCHIVE_OK == archive_read_data_block(a, &buff, &bytes_read,
290231200Smm	    &offset)) {
291231200Smm		if (offset > total || offset == 0) {
292231200Smm			if (offset > total)
293231200Smm				hole = 1;
294231200Smm			data_blocks++;
295231200Smm		}
296231200Smm		total = offset + bytes_read;
297231200Smm	}
298231200Smm	if (!hole && data_blocks == 1)
299231200Smm		data_blocks = 0;/* There are no holes */
300231200Smm	assertEqualInt(blocks, data_blocks);
301231200Smm
302231200Smm	assertEqualIntA(a, ARCHIVE_OK, archive_read_close(a));
303231200Smm	archive_entry_free(ae);
304231200Smm}
305231200Smm
306231200Smm#if defined(_WIN32) && !defined(__CYGWIN__)
307231200Smm#define	close		_close
308231200Smm#define	open		_open
309231200Smm#endif
310231200Smm
311231200Smm/*
312231200Smm * Sparse test without directory traversals.
313231200Smm */
314231200Smmstatic void
315231200Smmverify_sparse_file2(struct archive *a, const char *path,
316231200Smm    const struct sparse *sparse, int blocks, int preopen)
317231200Smm{
318231200Smm	struct archive_entry *ae;
319231200Smm	int fd;
320231200Smm
321231200Smm	(void)sparse; /* UNUSED */
322231200Smm	assert((ae = archive_entry_new()) != NULL);
323231200Smm	archive_entry_set_pathname(ae, path);
324231200Smm	if (preopen)
325231200Smm		fd = open(path, O_RDONLY | O_BINARY);
326231200Smm	else
327231200Smm		fd = -1;
328231200Smm	assertEqualIntA(a, ARCHIVE_OK,
329231200Smm	    archive_read_disk_entry_from_file(a, ae, fd, NULL));
330231200Smm	if (fd >= 0)
331231200Smm		close(fd);
332231200Smm	/* Verify the number of holes only, not its offset nor its
333231200Smm	 * length because those alignments are deeply dependence on
334231200Smm	 * its filesystem. */
335231200Smm	assertEqualInt(blocks, archive_entry_sparse_count(ae));
336231200Smm	archive_entry_free(ae);
337231200Smm}
338231200Smm
339231200Smmstatic void
340231200Smmtest_sparse_whole_file_data()
341231200Smm{
342231200Smm	struct archive_entry *ae;
343231200Smm	int64_t offset;
344231200Smm	int i;
345231200Smm
346231200Smm	assert((ae = archive_entry_new()) != NULL);
347231200Smm	archive_entry_set_size(ae, 1024*10);
348231200Smm
349231200Smm	/*
350231200Smm	 * Add sparse block data up to the file size.
351231200Smm	 */
352231200Smm	offset = 0;
353231200Smm	for (i = 0; i < 10; i++) {
354231200Smm		archive_entry_sparse_add_entry(ae, offset, 1024);
355231200Smm		offset += 1024;
356231200Smm	}
357231200Smm
358231200Smm	failure("There should be no sparse");
359231200Smm	assertEqualInt(0, archive_entry_sparse_count(ae));
360231200Smm	archive_entry_free(ae);
361231200Smm}
362231200Smm
363231200SmmDEFINE_TEST(test_sparse_basic)
364231200Smm{
365231200Smm	char *cwd;
366231200Smm	struct archive *a;
367231200Smm	/*
368231200Smm	 * The alignment of the hole of sparse files deeply depends
369231200Smm	 * on filesystem. In my experience, sparse_file2 test with
370231200Smm	 * 204800 bytes hole size did not pass on ZFS and the result
371231200Smm	 * of that test seemed the size was too small, thus you should
372231200Smm	 * keep a hole size more than 409600 bytes to pass this test
373231200Smm	 * on all platform.
374231200Smm	 */
375231200Smm	const struct sparse sparse_file0[] = {
376231200Smm		{ DATA,	 1024 }, { HOLE,   2048000 },
377231200Smm		{ DATA,	 2048 }, { HOLE,   2048000 },
378231200Smm		{ DATA,	 4096 }, { HOLE,  20480000 },
379231200Smm		{ DATA,	 8192 }, { HOLE, 204800000 },
380231200Smm		{ DATA,     1 }, { END,	0 }
381231200Smm	};
382231200Smm	const struct sparse sparse_file1[] = {
383231200Smm		{ HOLE,	409600 }, { DATA, 1 },
384231200Smm		{ HOLE,	409600 }, { DATA, 1 },
385231200Smm		{ HOLE,	409600 }, { END,  0 }
386231200Smm	};
387231200Smm	const struct sparse sparse_file2[] = {
388231200Smm		{ HOLE,	409600 * 1 }, { DATA, 1024 },
389231200Smm		{ HOLE,	409600 * 2 }, { DATA, 1024 },
390231200Smm		{ HOLE,	409600 * 3 }, { DATA, 1024 },
391231200Smm		{ HOLE,	409600 * 4 }, { DATA, 1024 },
392231200Smm		{ HOLE,	409600 * 5 }, { DATA, 1024 },
393231200Smm		{ HOLE,	409600 * 6 }, { DATA, 1024 },
394231200Smm		{ HOLE,	409600 * 7 }, { DATA, 1024 },
395231200Smm		{ HOLE,	409600 * 8 }, { DATA, 1024 },
396231200Smm		{ HOLE,	409600 * 9 }, { DATA, 1024 },
397231200Smm		{ HOLE,	409600 * 10}, { DATA, 1024 },/* 10 */
398231200Smm		{ HOLE,	409600 * 1 }, { DATA, 1024 * 1 },
399231200Smm		{ HOLE,	409600 * 2 }, { DATA, 1024 * 2 },
400231200Smm		{ HOLE,	409600 * 3 }, { DATA, 1024 * 3 },
401231200Smm		{ HOLE,	409600 * 4 }, { DATA, 1024 * 4 },
402231200Smm		{ HOLE,	409600 * 5 }, { DATA, 1024 * 5 },
403231200Smm		{ HOLE,	409600 * 6 }, { DATA, 1024 * 6 },
404231200Smm		{ HOLE,	409600 * 7 }, { DATA, 1024 * 7 },
405231200Smm		{ HOLE,	409600 * 8 }, { DATA, 1024 * 8 },
406231200Smm		{ HOLE,	409600 * 9 }, { DATA, 1024 * 9 },
407231200Smm		{ HOLE,	409600 * 10}, { DATA, 1024 * 10},/* 20 */
408231200Smm		{ END,	0 }
409231200Smm	};
410231200Smm	const struct sparse sparse_file3[] = {
411231200Smm 		/* This hole size is too small to create a sparse
412231200Smm		 * files for almost filesystem. */
413231200Smm		{ HOLE,	 1024 }, { DATA, 10240 },
414231200Smm		{ END,	0 }
415231200Smm	};
416231200Smm
417231200Smm	/*
418231200Smm	 * Test for the case that sparse data indicates just the whole file
419231200Smm	 * data.
420231200Smm	 */
421231200Smm	test_sparse_whole_file_data();
422231200Smm
423231200Smm	/* Check if the filesystem where CWD on can
424231200Smm	 * report the number of the holes of a sparse file. */
425231200Smm#ifdef PATH_MAX
426231200Smm	cwd = getcwd(NULL, PATH_MAX);/* Solaris getcwd needs the size. */
427231200Smm#else
428231200Smm	cwd = getcwd(NULL, 0);
429231200Smm#endif
430231200Smm	if (!assert(cwd != NULL))
431231200Smm		return;
432231200Smm	if (!is_sparse_supported(cwd)) {
433231200Smm		free(cwd);
434231200Smm		skipping("This filesystem or platform do not support "
435231200Smm		    "the reporting of the holes of a sparse file through "
436231200Smm		    "API such as lseek(HOLE)");
437231200Smm		return;
438231200Smm	}
439231200Smm
440231200Smm	/*
441231200Smm	 * Get sparse data through directory traversals.
442231200Smm	 */
443231200Smm	assert((a = archive_read_disk_new()) != NULL);
444231200Smm
445231200Smm	verify_sparse_file(a, "file0", sparse_file0, 5);
446231200Smm	verify_sparse_file(a, "file1", sparse_file1, 2);
447231200Smm	verify_sparse_file(a, "file2", sparse_file2, 20);
448231200Smm	verify_sparse_file(a, "file3", sparse_file3, 0);
449231200Smm
450231200Smm	assertEqualInt(ARCHIVE_OK, archive_read_free(a));
451231200Smm
452231200Smm	/*
453231200Smm	 * Get sparse data through archive_read_disk_entry_from_file().
454231200Smm	 */
455231200Smm	assert((a = archive_read_disk_new()) != NULL);
456231200Smm
457231200Smm	verify_sparse_file2(a, "file0", sparse_file0, 5, 0);
458231200Smm	verify_sparse_file2(a, "file0", sparse_file0, 5, 1);
459231200Smm
460231200Smm	assertEqualInt(ARCHIVE_OK, archive_read_free(a));
461231200Smm	free(cwd);
462231200Smm}
463