test_sparse_basic.c revision 337352
1/*- 2 * Copyright (c) 2010-2012 Michihiro NAKAJIMA 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 14 * 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_TYPES_H 44#include <linux/types.h> 45#endif 46#ifdef HAVE_LINUX_FIEMAP_H 47#include <linux/fiemap.h> 48#endif 49#ifdef HAVE_LINUX_FS_H 50#include <linux/fs.h> 51#endif 52 53/* The logic to compare sparse file data read from disk with the 54 * specification is a little involved. Set to 1 to have the progress 55 * dumped. */ 56#define DEBUG 0 57 58/* 59 * NOTE: On FreeBSD and Solaris, this test needs ZFS. 60 * You may perform this test as 61 * 'TMPDIR=<a directory on the ZFS> libarchive_test'. 62 */ 63 64struct sparse { 65 enum { DATA, HOLE, END } type; 66 size_t size; 67}; 68 69static void create_sparse_file(const char *, const struct sparse *); 70 71#if defined(_WIN32) && !defined(__CYGWIN__) 72#include <winioctl.h> 73/* 74 * Create a sparse file on Windows. 75 */ 76 77#if !defined(PATH_MAX) 78#define PATH_MAX MAX_PATH 79#endif 80#if !defined(__BORLANDC__) 81#define getcwd _getcwd 82#endif 83 84static int 85is_sparse_supported(const char *path) 86{ 87 char root[MAX_PATH+1]; 88 char vol[MAX_PATH+1]; 89 char sys[MAX_PATH+1]; 90 DWORD flags; 91 BOOL r; 92 93 strncpy(root, path, sizeof(root)-1); 94 if (((root[0] >= 'c' && root[0] <= 'z') || 95 (root[0] >= 'C' && root[0] <= 'Z')) && 96 root[1] == ':' && 97 (root[2] == '\\' || root[2] == '/')) 98 root[3] = '\0'; 99 else 100 return (0); 101 assertEqualInt((r = GetVolumeInformation(root, vol, 102 sizeof(vol), NULL, NULL, &flags, sys, sizeof(sys))), 1); 103 return (r != 0 && (flags & FILE_SUPPORTS_SPARSE_FILES) != 0); 104} 105 106static void 107create_sparse_file(const char *path, const struct sparse *s) 108{ 109 char buff[1024]; 110 HANDLE handle; 111 DWORD dmy; 112 113 memset(buff, ' ', sizeof(buff)); 114 115 handle = CreateFileA(path, GENERIC_WRITE, 0, 116 NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, 117 NULL); 118 assert(handle != INVALID_HANDLE_VALUE); 119 assert(DeviceIoControl(handle, FSCTL_SET_SPARSE, NULL, 0, 120 NULL, 0, &dmy, NULL) != 0); 121 122 size_t offsetSoFar = 0; 123 124 while (s->type != END) { 125 if (s->type == HOLE) { 126 LARGE_INTEGER fileOffset, beyondOffset, distanceToMove; 127 fileOffset.QuadPart = offsetSoFar; 128 beyondOffset.QuadPart = offsetSoFar + s->size; 129 distanceToMove.QuadPart = s->size; 130 131 FILE_ZERO_DATA_INFORMATION zeroInformation; 132 zeroInformation.FileOffset = fileOffset; 133 zeroInformation.BeyondFinalZero = beyondOffset; 134 135 DWORD bytesReturned; 136 assert(SetFilePointerEx(handle, distanceToMove, 137 NULL, FILE_CURRENT) != 0); 138 assert(SetEndOfFile(handle) != 0); 139 assert(DeviceIoControl(handle, FSCTL_SET_ZERO_DATA, &zeroInformation, 140 sizeof(FILE_ZERO_DATA_INFORMATION), NULL, 0, &bytesReturned, NULL) != 0); 141 } else { 142 DWORD w, wr; 143 size_t size; 144 145 size = s->size; 146 while (size) { 147 if (size > sizeof(buff)) 148 w = sizeof(buff); 149 else 150 w = (DWORD)size; 151 assert(WriteFile(handle, buff, w, &wr, NULL) != 0); 152 size -= wr; 153 } 154 } 155 offsetSoFar += s->size; 156 s++; 157 } 158 assertEqualInt(CloseHandle(handle), 1); 159} 160 161#else 162 163#if defined(HAVE_LINUX_FIEMAP_H) 164/* 165 * FIEMAP, which can detect 'hole' of a sparse file, has 166 * been supported from 2.6.28 167 */ 168 169static int 170is_sparse_supported_fiemap(const char *path) 171{ 172 const struct sparse sparse_file[] = { 173 /* This hole size is too small to create a sparse 174 * files for almost filesystem. */ 175 { HOLE, 1024 }, { DATA, 10240 }, 176 { END, 0 } 177 }; 178 int fd, r; 179 struct fiemap *fm; 180 char buff[1024]; 181 const char *testfile = "can_sparse"; 182 183 (void)path; /* UNUSED */ 184 memset(buff, 0, sizeof(buff)); 185 create_sparse_file(testfile, sparse_file); 186 fd = open(testfile, O_RDWR); 187 if (fd < 0) 188 return (0); 189 fm = (struct fiemap *)buff; 190 fm->fm_start = 0; 191 fm->fm_length = ~0ULL;; 192 fm->fm_flags = FIEMAP_FLAG_SYNC; 193 fm->fm_extent_count = (sizeof(buff) - sizeof(*fm))/ 194 sizeof(struct fiemap_extent); 195 r = ioctl(fd, FS_IOC_FIEMAP, fm); 196 close(fd); 197 unlink(testfile); 198 return (r >= 0); 199} 200 201#if !defined(SEEK_HOLE) || !defined(SEEK_DATA) 202static int 203is_sparse_supported(const char *path) 204{ 205 return is_sparse_supported_fiemap(path); 206} 207#endif 208#endif 209 210#if defined(_PC_MIN_HOLE_SIZE) 211 212/* 213 * FreeBSD and Solaris can detect 'hole' of a sparse file 214 * through lseek(HOLE) on ZFS. (UFS does not support yet) 215 */ 216 217static int 218is_sparse_supported(const char *path) 219{ 220 return (pathconf(path, _PC_MIN_HOLE_SIZE) > 0); 221} 222 223#elif defined(SEEK_HOLE) && defined(SEEK_DATA) 224 225static int 226is_sparse_supported(const char *path) 227{ 228 const struct sparse sparse_file[] = { 229 /* This hole size is too small to create a sparse 230 * files for almost filesystem. */ 231 { HOLE, 1024 }, { DATA, 10240 }, 232 { END, 0 } 233 }; 234 int fd, r; 235 const char *testfile = "can_sparse"; 236 237 (void)path; /* UNUSED */ 238 create_sparse_file(testfile, sparse_file); 239 fd = open(testfile, O_RDWR); 240 if (fd < 0) 241 return (0); 242 r = lseek(fd, 0, SEEK_HOLE); 243 close(fd); 244 unlink(testfile); 245#if defined(HAVE_LINUX_FIEMAP_H) 246 if (r < 0) 247 return (is_sparse_supported_fiemap(path)); 248#endif 249 return (r >= 0); 250} 251 252#elif !defined(HAVE_LINUX_FIEMAP_H) 253 254/* 255 * Other system may do not have the API such as lseek(HOLE), 256 * which detect 'hole' of a sparse file. 257 */ 258 259static int 260is_sparse_supported(const char *path) 261{ 262 (void)path; /* UNUSED */ 263 return (0); 264} 265 266#endif 267 268/* 269 * Create a sparse file on POSIX like system. 270 */ 271 272static void 273create_sparse_file(const char *path, const struct sparse *s) 274{ 275 char buff[1024]; 276 int fd; 277 size_t total_size = 0; 278 const struct sparse *cur = s; 279 280 memset(buff, ' ', sizeof(buff)); 281 assert((fd = open(path, O_CREAT | O_WRONLY, 0600)) != -1); 282 283 /* Handle holes at the end by extending the file */ 284 while (cur->type != END) { 285 total_size += cur->size; 286 ++cur; 287 } 288 assert(ftruncate(fd, total_size) != -1); 289 290 while (s->type != END) { 291 if (s->type == HOLE) { 292 assert(lseek(fd, s->size, SEEK_CUR) != (off_t)-1); 293 } else { 294 size_t w, size; 295 296 size = s->size; 297 while (size) { 298 if (size > sizeof(buff)) 299 w = sizeof(buff); 300 else 301 w = size; 302 assert(write(fd, buff, w) != (ssize_t)-1); 303 size -= w; 304 } 305 } 306 s++; 307 } 308 close(fd); 309} 310 311#endif 312 313/* 314 * Sparse test with directory traversals. 315 */ 316static void 317verify_sparse_file(struct archive *a, const char *path, 318 const struct sparse *sparse, int expected_holes) 319{ 320 struct archive_entry *ae; 321 const void *buff; 322 size_t bytes_read; 323 int64_t offset, expected_offset, last_offset; 324 int holes_seen = 0; 325 326 create_sparse_file(path, sparse); 327 assert((ae = archive_entry_new()) != NULL); 328 assertEqualIntA(a, ARCHIVE_OK, archive_read_disk_open(a, path)); 329 assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header2(a, ae)); 330 331 expected_offset = 0; 332 last_offset = 0; 333 while (ARCHIVE_OK == archive_read_data_block(a, &buff, &bytes_read, 334 &offset)) { 335 const char *start = buff; 336#if DEBUG 337 fprintf(stderr, "%s: bytes_read=%d offset=%d\n", path, (int)bytes_read, (int)offset); 338#endif 339 if (offset > last_offset) { 340 ++holes_seen; 341 } 342 /* Blocks entirely before the data we just read. */ 343 while (expected_offset + (int64_t)sparse->size < offset) { 344#if DEBUG 345 fprintf(stderr, " skipping expected_offset=%d, size=%d\n", (int)expected_offset, (int)sparse->size); 346#endif 347 /* Must be holes. */ 348 assert(sparse->type == HOLE); 349 expected_offset += sparse->size; 350 ++sparse; 351 } 352 /* Block that overlaps beginning of data */ 353 if (expected_offset < offset 354 && expected_offset + (int64_t)sparse->size <= offset + (int64_t)bytes_read) { 355 const char *end = (const char *)buff + (expected_offset - offset) + (size_t)sparse->size; 356#if DEBUG 357 fprintf(stderr, " overlapping hole expected_offset=%d, size=%d\n", (int)expected_offset, (int)sparse->size); 358#endif 359 /* Must be a hole, overlap must be filled with '\0' */ 360 if (assert(sparse->type == HOLE)) { 361 assertMemoryFilledWith(start, end - start, '\0'); 362 } 363 start = end; 364 expected_offset += sparse->size; 365 ++sparse; 366 } 367 /* Blocks completely contained in data we just read. */ 368 while (expected_offset + (int64_t)sparse->size <= offset + (int64_t)bytes_read) { 369 const char *end = (const char *)buff + (expected_offset - offset) + (size_t)sparse->size; 370 if (sparse->type == HOLE) { 371#if DEBUG 372 fprintf(stderr, " contained hole expected_offset=%d, size=%d\n", (int)expected_offset, (int)sparse->size); 373#endif 374 375 /* verify data corresponding to hole is '\0' */ 376 if (end > (const char *)buff + bytes_read) { 377 end = (const char *)buff + bytes_read; 378 } 379 assertMemoryFilledWith(start, end - start, '\0'); 380 start = end; 381 expected_offset += sparse->size; 382 ++sparse; 383 } else if (sparse->type == DATA) { 384#if DEBUG 385 fprintf(stderr, " contained data expected_offset=%d, size=%d\n", (int)expected_offset, (int)sparse->size); 386#endif 387 /* verify data corresponding to hole is ' ' */ 388 if (assert(expected_offset + sparse->size <= offset + bytes_read)) { 389 assert(start == (const char *)buff + (size_t)(expected_offset - offset)); 390 assertMemoryFilledWith(start, end - start, ' '); 391 } 392 start = end; 393 expected_offset += sparse->size; 394 ++sparse; 395 } else { 396 break; 397 } 398 } 399 /* Block that overlaps end of data */ 400 if (expected_offset < offset + (int64_t)bytes_read) { 401 const char *end = (const char *)buff + bytes_read; 402#if DEBUG 403 fprintf(stderr, " trailing overlap expected_offset=%d, size=%d\n", (int)expected_offset, (int)sparse->size); 404#endif 405 /* Must be a hole, overlap must be filled with '\0' */ 406 if (assert(sparse->type == HOLE)) { 407 assertMemoryFilledWith(start, end - start, '\0'); 408 } 409 } 410 last_offset = offset + bytes_read; 411 } 412 /* Count a hole at EOF? */ 413 if (last_offset < archive_entry_size(ae)) { 414 ++holes_seen; 415 } 416 417 /* Verify blocks after last read */ 418 while (sparse->type == HOLE) { 419 expected_offset += sparse->size; 420 ++sparse; 421 } 422 assert(sparse->type == END); 423 assertEqualInt(expected_offset, archive_entry_size(ae)); 424 425 assertEqualInt(holes_seen, expected_holes); 426 427 assertEqualIntA(a, ARCHIVE_OK, archive_read_close(a)); 428 archive_entry_free(ae); 429} 430 431#if defined(_WIN32) && !defined(__CYGWIN__) 432#define close _close 433#define open _open 434#endif 435 436/* 437 * Sparse test without directory traversals. 438 */ 439static void 440verify_sparse_file2(struct archive *a, const char *path, 441 const struct sparse *sparse, int blocks, int preopen) 442{ 443 struct archive_entry *ae; 444 int fd; 445 446 (void)sparse; /* UNUSED */ 447 assert((ae = archive_entry_new()) != NULL); 448 archive_entry_set_pathname(ae, path); 449 if (preopen) 450 fd = open(path, O_RDONLY | O_BINARY); 451 else 452 fd = -1; 453 assertEqualIntA(a, ARCHIVE_OK, 454 archive_read_disk_entry_from_file(a, ae, fd, NULL)); 455 if (fd >= 0) 456 close(fd); 457 /* Verify the number of holes only, not its offset nor its 458 * length because those alignments are deeply dependence on 459 * its filesystem. */ 460 assertEqualInt(blocks, archive_entry_sparse_count(ae)); 461 archive_entry_free(ae); 462} 463 464static void 465test_sparse_whole_file_data() 466{ 467 struct archive_entry *ae; 468 int64_t offset; 469 int i; 470 471 assert((ae = archive_entry_new()) != NULL); 472 archive_entry_set_size(ae, 1024*10); 473 474 /* 475 * Add sparse block data up to the file size. 476 */ 477 offset = 0; 478 for (i = 0; i < 10; i++) { 479 archive_entry_sparse_add_entry(ae, offset, 1024); 480 offset += 1024; 481 } 482 483 failure("There should be no sparse"); 484 assertEqualInt(0, archive_entry_sparse_count(ae)); 485 archive_entry_free(ae); 486} 487 488DEFINE_TEST(test_sparse_basic) 489{ 490 char *cwd; 491 struct archive *a; 492 /* 493 * The alignment of the hole of sparse files deeply depends 494 * on filesystem. In my experience, sparse_file2 test with 495 * 204800 bytes hole size did not pass on ZFS and the result 496 * of that test seemed the size was too small, thus you should 497 * keep a hole size more than 409600 bytes to pass this test 498 * on all platform. 499 */ 500 const struct sparse sparse_file0[] = { 501 // 0 // 1024 502 { DATA, 1024 }, { HOLE, 2048000 }, 503 // 2049024 // 2051072 504 { DATA, 2048 }, { HOLE, 2048000 }, 505 // 4099072 // 4103168 506 { DATA, 4096 }, { HOLE, 20480000 }, 507 // 24583168 // 24591360 508 { DATA, 8192 }, { HOLE, 204800000 }, 509 // 229391360 // 229391361 510 { DATA, 1 }, { END, 0 } 511 }; 512 const struct sparse sparse_file1[] = { 513 { HOLE, 409600 }, { DATA, 1 }, 514 { HOLE, 409600 }, { DATA, 1 }, 515 { HOLE, 409600 }, { END, 0 } 516 }; 517 const struct sparse sparse_file2[] = { 518 { HOLE, 409600 * 1 }, { DATA, 1024 }, 519 { HOLE, 409600 * 2 }, { DATA, 1024 }, 520 { HOLE, 409600 * 3 }, { DATA, 1024 }, 521 { HOLE, 409600 * 4 }, { DATA, 1024 }, 522 { HOLE, 409600 * 5 }, { DATA, 1024 }, 523 { HOLE, 409600 * 6 }, { DATA, 1024 }, 524 { HOLE, 409600 * 7 }, { DATA, 1024 }, 525 { HOLE, 409600 * 8 }, { DATA, 1024 }, 526 { HOLE, 409600 * 9 }, { DATA, 1024 }, 527 { HOLE, 409600 * 10}, { DATA, 1024 },/* 10 */ 528 { HOLE, 409600 * 1 }, { DATA, 1024 * 1 }, 529 { HOLE, 409600 * 2 }, { DATA, 1024 * 2 }, 530 { HOLE, 409600 * 3 }, { DATA, 1024 * 3 }, 531 { HOLE, 409600 * 4 }, { DATA, 1024 * 4 }, 532 { HOLE, 409600 * 5 }, { DATA, 1024 * 5 }, 533 { HOLE, 409600 * 6 }, { DATA, 1024 * 6 }, 534 { HOLE, 409600 * 7 }, { DATA, 1024 * 7 }, 535 { HOLE, 409600 * 8 }, { DATA, 1024 * 8 }, 536 { HOLE, 409600 * 9 }, { DATA, 1024 * 9 }, 537 { HOLE, 409600 * 10}, { DATA, 1024 * 10},/* 20 */ 538 { END, 0 } 539 }; 540 const struct sparse sparse_file3[] = { 541 /* This hole size is too small to create a sparse file */ 542 { HOLE, 1 }, { DATA, 10240 }, 543 { HOLE, 1 }, { DATA, 10240 }, 544 { HOLE, 1 }, { DATA, 10240 }, 545 { END, 0 } 546 }; 547 548 /* 549 * Test for the case that sparse data indicates just the whole file 550 * data. 551 */ 552 test_sparse_whole_file_data(); 553 554 /* Check if the filesystem where CWD on can 555 * report the number of the holes of a sparse file. */ 556#ifdef PATH_MAX 557 cwd = getcwd(NULL, PATH_MAX);/* Solaris getcwd needs the size. */ 558#else 559 cwd = getcwd(NULL, 0); 560#endif 561 if (!assert(cwd != NULL)) 562 return; 563 if (!is_sparse_supported(cwd)) { 564 free(cwd); 565 skipping("This filesystem or platform do not support " 566 "the reporting of the holes of a sparse file through " 567 "API such as lseek(HOLE)"); 568 return; 569 } 570 571 /* 572 * Get sparse data through directory traversals. 573 */ 574 assert((a = archive_read_disk_new()) != NULL); 575 576 verify_sparse_file(a, "file0", sparse_file0, 4); 577 verify_sparse_file(a, "file1", sparse_file1, 3); 578 verify_sparse_file(a, "file2", sparse_file2, 20); 579 /* Encoded non sparse; expect a data block but no sparse entries. */ 580 verify_sparse_file(a, "file3", sparse_file3, 0); 581 582 assertEqualInt(ARCHIVE_OK, archive_read_free(a)); 583 584 /* 585 * Get sparse data through archive_read_disk_entry_from_file(). 586 */ 587 assert((a = archive_read_disk_new()) != NULL); 588 589 verify_sparse_file2(a, "file0", sparse_file0, 5, 0); 590 verify_sparse_file2(a, "file0", sparse_file0, 5, 1); 591 592 assertEqualInt(ARCHIVE_OK, archive_read_free(a)); 593 free(cwd); 594} 595 596DEFINE_TEST(test_fully_sparse_files) 597{ 598 char *cwd; 599 struct archive *a; 600 601 const struct sparse sparse_file[] = { 602 { HOLE, 409600 }, { END, 0 } 603 }; 604 /* Check if the filesystem where CWD on can 605 * report the number of the holes of a sparse file. */ 606#ifdef PATH_MAX 607 cwd = getcwd(NULL, PATH_MAX);/* Solaris getcwd needs the size. */ 608#else 609 cwd = getcwd(NULL, 0); 610#endif 611 if (!assert(cwd != NULL)) 612 return; 613 if (!is_sparse_supported(cwd)) { 614 free(cwd); 615 skipping("This filesystem or platform do not support " 616 "the reporting of the holes of a sparse file through " 617 "API such as lseek(HOLE)"); 618 return; 619 } 620 621 assert((a = archive_read_disk_new()) != NULL); 622 623 /* Fully sparse files are encoded with a zero-length "data" block. */ 624 verify_sparse_file(a, "file0", sparse_file, 1); 625 626 assertEqualInt(ARCHIVE_OK, archive_read_free(a)); 627 free(cwd); 628} 629