test_sparse_basic.c revision 238856
1169092Sdeischen/*- 2169092Sdeischen * Copyright (c) 2010-2012 Michihiro NAKAJIMA 3169092Sdeischen * All rights reserved. 4156608Sdeischen * 5156608Sdeischen * Redistribution and use in source and binary forms, with or without 6169092Sdeischen * modification, are permitted provided that the following conditions 7156608Sdeischen * are met: 8156608Sdeischen * 1. Redistributions of source code must retain the above copyright 9156608Sdeischen * notice, this list of conditions and the following disclaimer. 10156608Sdeischen * 2. Redistributions in binary form must reproduce the above copyright 11156608Sdeischen * notice, this list of conditions and the following disclaimer in the 12169092Sdeischen * documentation and/or other materials provided with the distribution. 13156608Sdeischen * 14156608Sdeischen * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR 15 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 16 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 17 * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, 18 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 19 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 20 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 21 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 23 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 */ 25#include "test.h" 26__FBSDID("$FreeBSD$"); 27 28#ifdef HAVE_SYS_IOCTL_H 29#include <sys/ioctl.h> 30#endif 31#ifdef HAVE_SYS_PARAM_H 32#include <sys/param.h> 33#endif 34#ifdef HAVE_FCNTL_H 35#include <fcntl.h> 36#endif 37#ifdef HAVE_LIMITS_H 38#include <limits.h> 39#endif 40#ifdef HAVE_UNISTD_H 41#include <unistd.h> 42#endif 43#ifdef HAVE_LINUX_FIEMAP_H 44#include <linux/fiemap.h> 45#endif 46#ifdef HAVE_LINUX_FS_H 47#include <linux/fs.h> 48#endif 49 50/* 51 * NOTE: On FreeBSD and Solaris, this test needs ZFS. 52 * You may should perfom this test as 53 * 'TMPDIR=<a directory on the ZFS> libarchive_test'. 54 */ 55 56struct sparse { 57 enum { DATA, HOLE, END } type; 58 size_t size; 59}; 60 61static void create_sparse_file(const char *, const struct sparse *); 62 63#if defined(_WIN32) && !defined(__CYGWIN__) 64#include <winioctl.h> 65/* 66 * Create a sparse file on Windows. 67 */ 68 69#if !defined(PATH_MAX) 70#define PATH_MAX MAX_PATH 71#endif 72#if !defined(__BORLANDC__) 73#define getcwd _getcwd 74#endif 75 76static int 77is_sparse_supported(const char *path) 78{ 79 char root[MAX_PATH+1]; 80 char vol[MAX_PATH+1]; 81 char sys[MAX_PATH+1]; 82 DWORD flags; 83 BOOL r; 84 85 strncpy(root, path, sizeof(root)-1); 86 if (((root[0] >= 'c' && root[0] <= 'z') || 87 (root[0] >= 'C' && root[0] <= 'Z')) && 88 root[1] == ':' && 89 (root[2] == '\\' || root[2] == '/')) 90 root[3] = '\0'; 91 else 92 return (0); 93 assertEqualInt((r = GetVolumeInformation(root, vol, 94 sizeof(vol), NULL, NULL, &flags, sys, sizeof(sys))), 1); 95 return (r != 0 && (flags & FILE_SUPPORTS_SPARSE_FILES) != 0); 96} 97 98static void 99create_sparse_file(const char *path, const struct sparse *s) 100{ 101 char buff[1024]; 102 HANDLE handle; 103 DWORD dmy; 104 105 memset(buff, ' ', sizeof(buff)); 106 107 handle = CreateFileA(path, GENERIC_WRITE, 0, 108 NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, 109 NULL); 110 assert(handle != INVALID_HANDLE_VALUE); 111 assert(DeviceIoControl(handle, FSCTL_SET_SPARSE, NULL, 0, 112 NULL, 0, &dmy, NULL) != 0); 113 while (s->type != END) { 114 if (s->type == HOLE) { 115 LARGE_INTEGER distance; 116 117 distance.QuadPart = s->size; 118 assert(SetFilePointerEx(handle, distance, 119 NULL, FILE_CURRENT) != 0); 120 } else { 121 DWORD w, wr, size; 122 123 size = s->size; 124 while (size) { 125 if (size > sizeof(buff)) 126 w = sizeof(buff); 127 else 128 w = size; 129 assert(WriteFile(handle, buff, w, &wr, NULL) != 0); 130 size -= wr; 131 } 132 } 133 s++; 134 } 135 assertEqualInt(CloseHandle(handle), 1); 136} 137 138#else 139 140#if defined(_PC_MIN_HOLE_SIZE) 141 142/* 143 * FreeBSD and Solaris can detect 'hole' of a sparse file 144 * through lseek(HOLE) on ZFS. (UFS does not support yet) 145 */ 146 147static int 148is_sparse_supported(const char *path) 149{ 150 return (pathconf(path, _PC_MIN_HOLE_SIZE) > 0); 151} 152 153#elif defined(__linux__)&& defined(HAVE_LINUX_FIEMAP_H) 154 155/* 156 * FIEMAP, which can detect 'hole' of a sparse file, has 157 * been supported from 2.6.28 158 */ 159 160static int 161is_sparse_supported(const char *path) 162{ 163 const struct sparse sparse_file[] = { 164 /* This hole size is too small to create a sparse 165 * files for almost filesystem. */ 166 { HOLE, 1024 }, { DATA, 10240 }, 167 { END, 0 } 168 }; 169 int fd, r; 170 struct fiemap *fm; 171 char buff[1024]; 172 const char *testfile = "can_sparse"; 173 174 (void)path; /* UNUSED */ 175 create_sparse_file(testfile, sparse_file); 176 fd = open(testfile, O_RDWR); 177 if (fd < 0) 178 return (0); 179 fm = (struct fiemap *)buff; 180 fm->fm_start = 0; 181 fm->fm_length = ~0ULL;; 182 fm->fm_flags = FIEMAP_FLAG_SYNC; 183 fm->fm_extent_count = (sizeof(buff) - sizeof(*fm))/ 184 sizeof(struct fiemap_extent); 185 r = ioctl(fd, FS_IOC_FIEMAP, fm); 186 close(fd); 187 unlink(testfile); 188 return (r >= 0); 189} 190 191#else 192 193/* 194 * Other system may do not have the API such as lseek(HOLE), 195 * which detect 'hole' of a sparse file. 196 */ 197 198static int 199is_sparse_supported(const char *path) 200{ 201 (void)path; /* UNUSED */ 202 return (0); 203} 204 205#endif 206 207/* 208 * Create a sparse file on POSIX like system. 209 */ 210 211static void 212create_sparse_file(const char *path, const struct sparse *s) 213{ 214 char buff[1024]; 215 int fd; 216 217 memset(buff, ' ', sizeof(buff)); 218 assert((fd = open(path, O_CREAT | O_WRONLY, 0600)) != -1); 219 while (s->type != END) { 220 if (s->type == HOLE) { 221 assert(lseek(fd, s->size, SEEK_CUR) != (off_t)-1); 222 } else { 223 size_t w, size; 224 225 size = s->size; 226 while (size) { 227 if (size > sizeof(buff)) 228 w = sizeof(buff); 229 else 230 w = size; 231 assert(write(fd, buff, w) != (ssize_t)-1); 232 size -= w; 233 } 234 } 235 s++; 236 } 237 close(fd); 238} 239 240#endif 241 242/* 243 * Sparse test with directory traversals. 244 */ 245static void 246verify_sparse_file(struct archive *a, const char *path, 247 const struct sparse *sparse, int blocks) 248{ 249 struct archive_entry *ae; 250 const void *buff; 251 size_t bytes_read; 252 int64_t offset; 253 int64_t total; 254 int data_blocks, hole; 255 256 create_sparse_file(path, sparse); 257 assert((ae = archive_entry_new()) != NULL); 258 assertEqualIntA(a, ARCHIVE_OK, archive_read_disk_open(a, path)); 259 assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header2(a, ae)); 260 /* Verify the number of holes only, not its offset nor its 261 * length because those alignments are deeply dependence on 262 * its filesystem. */ 263 assertEqualInt(blocks, archive_entry_sparse_count(ae)); 264 total = 0; 265 data_blocks = 0; 266 hole = 0; 267 while (ARCHIVE_OK == archive_read_data_block(a, &buff, &bytes_read, 268 &offset)) { 269 if (offset > total || offset == 0) { 270 if (offset > total) 271 hole = 1; 272 data_blocks++; 273 } 274 total = offset + bytes_read; 275 } 276 if (!hole && data_blocks == 1) 277 data_blocks = 0;/* There are no holes */ 278 assertEqualInt(blocks, data_blocks); 279 280 assertEqualIntA(a, ARCHIVE_OK, archive_read_close(a)); 281 archive_entry_free(ae); 282} 283 284#if defined(_WIN32) && !defined(__CYGWIN__) 285#define close _close 286#define open _open 287#endif 288 289/* 290 * Sparse test without directory traversals. 291 */ 292static void 293verify_sparse_file2(struct archive *a, const char *path, 294 const struct sparse *sparse, int blocks, int preopen) 295{ 296 struct archive_entry *ae; 297 int fd; 298 299 (void)sparse; /* UNUSED */ 300 assert((ae = archive_entry_new()) != NULL); 301 archive_entry_set_pathname(ae, path); 302 if (preopen) 303 fd = open(path, O_RDONLY | O_BINARY); 304 else 305 fd = -1; 306 assertEqualIntA(a, ARCHIVE_OK, 307 archive_read_disk_entry_from_file(a, ae, fd, NULL)); 308 if (fd >= 0) 309 close(fd); 310 /* Verify the number of holes only, not its offset nor its 311 * length because those alignments are deeply dependence on 312 * its filesystem. */ 313 assertEqualInt(blocks, archive_entry_sparse_count(ae)); 314 archive_entry_free(ae); 315} 316 317static void 318test_sparse_whole_file_data() 319{ 320 struct archive_entry *ae; 321 int64_t offset; 322 int i; 323 324 assert((ae = archive_entry_new()) != NULL); 325 archive_entry_set_size(ae, 1024*10); 326 327 /* 328 * Add sparse block data up to the file size. 329 */ 330 offset = 0; 331 for (i = 0; i < 10; i++) { 332 archive_entry_sparse_add_entry(ae, offset, 1024); 333 offset += 1024; 334 } 335 336 failure("There should be no sparse"); 337 assertEqualInt(0, archive_entry_sparse_count(ae)); 338 archive_entry_free(ae); 339} 340 341DEFINE_TEST(test_sparse_basic) 342{ 343 char *cwd; 344 struct archive *a; 345 /* 346 * The alignment of the hole of sparse files deeply depends 347 * on filesystem. In my experience, sparse_file2 test with 348 * 204800 bytes hole size did not pass on ZFS and the result 349 * of that test seemed the size was too small, thus you should 350 * keep a hole size more than 409600 bytes to pass this test 351 * on all platform. 352 */ 353 const struct sparse sparse_file0[] = { 354 { DATA, 1024 }, { HOLE, 2048000 }, 355 { DATA, 2048 }, { HOLE, 2048000 }, 356 { DATA, 4096 }, { HOLE, 20480000 }, 357 { DATA, 8192 }, { HOLE, 204800000 }, 358 { DATA, 1 }, { END, 0 } 359 }; 360 const struct sparse sparse_file1[] = { 361 { HOLE, 409600 }, { DATA, 1 }, 362 { HOLE, 409600 }, { DATA, 1 }, 363 { HOLE, 409600 }, { END, 0 } 364 }; 365 const struct sparse sparse_file2[] = { 366 { HOLE, 409600 * 1 }, { DATA, 1024 }, 367 { HOLE, 409600 * 2 }, { DATA, 1024 }, 368 { HOLE, 409600 * 3 }, { DATA, 1024 }, 369 { HOLE, 409600 * 4 }, { DATA, 1024 }, 370 { HOLE, 409600 * 5 }, { DATA, 1024 }, 371 { HOLE, 409600 * 6 }, { DATA, 1024 }, 372 { HOLE, 409600 * 7 }, { DATA, 1024 }, 373 { HOLE, 409600 * 8 }, { DATA, 1024 }, 374 { HOLE, 409600 * 9 }, { DATA, 1024 }, 375 { HOLE, 409600 * 10}, { DATA, 1024 },/* 10 */ 376 { HOLE, 409600 * 1 }, { DATA, 1024 * 1 }, 377 { HOLE, 409600 * 2 }, { DATA, 1024 * 2 }, 378 { HOLE, 409600 * 3 }, { DATA, 1024 * 3 }, 379 { HOLE, 409600 * 4 }, { DATA, 1024 * 4 }, 380 { HOLE, 409600 * 5 }, { DATA, 1024 * 5 }, 381 { HOLE, 409600 * 6 }, { DATA, 1024 * 6 }, 382 { HOLE, 409600 * 7 }, { DATA, 1024 * 7 }, 383 { HOLE, 409600 * 8 }, { DATA, 1024 * 8 }, 384 { HOLE, 409600 * 9 }, { DATA, 1024 * 9 }, 385 { HOLE, 409600 * 10}, { DATA, 1024 * 10},/* 20 */ 386 { END, 0 } 387 }; 388 const struct sparse sparse_file3[] = { 389 /* This hole size is too small to create a sparse 390 * files for almost filesystem. */ 391 { HOLE, 1024 }, { DATA, 10240 }, 392 { END, 0 } 393 }; 394 395 /* 396 * Test for the case that sparse data indicates just the whole file 397 * data. 398 */ 399 test_sparse_whole_file_data(); 400 401 /* Check if the filesystem where CWD on can 402 * report the number of the holes of a sparse file. */ 403#ifdef PATH_MAX 404 cwd = getcwd(NULL, PATH_MAX);/* Solaris getcwd needs the size. */ 405#else 406 cwd = getcwd(NULL, 0); 407#endif 408 if (!assert(cwd != NULL)) 409 return; 410 if (!is_sparse_supported(cwd)) { 411 free(cwd); 412 skipping("This filesystem or platform do not support " 413 "the reporting of the holes of a sparse file through " 414 "API such as lseek(HOLE)"); 415 return; 416 } 417 418 /* 419 * Get sparse data through directory traversals. 420 */ 421 assert((a = archive_read_disk_new()) != NULL); 422 423 verify_sparse_file(a, "file0", sparse_file0, 5); 424 verify_sparse_file(a, "file1", sparse_file1, 2); 425 verify_sparse_file(a, "file2", sparse_file2, 20); 426 verify_sparse_file(a, "file3", sparse_file3, 0); 427 428 assertEqualInt(ARCHIVE_OK, archive_read_free(a)); 429 430 /* 431 * Get sparse data through archive_read_disk_entry_from_file(). 432 */ 433 assert((a = archive_read_disk_new()) != NULL); 434 435 verify_sparse_file2(a, "file0", sparse_file0, 5, 0); 436 verify_sparse_file2(a, "file0", sparse_file0, 5, 1); 437 438 assertEqualInt(ARCHIVE_OK, archive_read_free(a)); 439 free(cwd); 440} 441