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_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 56 struct sparse { 57 enum { DATA, HOLE, END } type; 58 size_t size; 59 }; 60 61 static 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 76 static int 77 is_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 98 static void 99 create_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; 122 size_t size; 123 124 size = s->size; 125 while (size) { 126 if (size > sizeof(buff)) 127 w = sizeof(buff); 128 else 129 w = (DWORD)size; 130 assert(WriteFile(handle, buff, w, &wr, NULL) != 0); 131 size -= wr; 132 } 133 } 134 s++; 135 } 136 assertEqualInt(CloseHandle(handle), 1); 137 } 138 139 #else 140 141 #if defined(_PC_MIN_HOLE_SIZE) 142 143 /* 144 * FreeBSD and Solaris can detect 'hole' of a sparse file 145 * through lseek(HOLE) on ZFS. (UFS does not support yet) 146 */ 147 148 static int 149 is_sparse_supported(const char *path) 150 { 151 return (pathconf(path, _PC_MIN_HOLE_SIZE) > 0); 152 } 153 154 #elif defined(__linux__)&& defined(HAVE_LINUX_FIEMAP_H) 155 156 /* 157 * FIEMAP, which can detect 'hole' of a sparse file, has 158 * been supported from 2.6.28 159 */ 160 161 static int 162 is_sparse_supported(const char *path) 163 { 164 const struct sparse sparse_file[] = { 165 /* This hole size is too small to create a sparse 166 * files for almost filesystem. */ 167 { HOLE, 1024 }, { DATA, 10240 }, 168 { END, 0 } 169 }; 170 int fd, r; 171 struct fiemap *fm; 172 char buff[1024]; 173 const char *testfile = "can_sparse"; 174 175 (void)path; /* UNUSED */ 176 memset(buff, 0, sizeof(buff)); 177 create_sparse_file(testfile, sparse_file); 178 fd = open(testfile, O_RDWR); 179 if (fd < 0) 180 return (0); 181 fm = (struct fiemap *)buff; 182 fm->fm_start = 0; 183 fm->fm_length = ~0ULL;; 184 fm->fm_flags = FIEMAP_FLAG_SYNC; 185 fm->fm_extent_count = (sizeof(buff) - sizeof(*fm))/ 186 sizeof(struct fiemap_extent); 187 r = ioctl(fd, FS_IOC_FIEMAP, fm); 188 close(fd); 189 unlink(testfile); 190 return (r >= 0); 191 } 192 193 #else 194 195 /* 196 * Other system may do not have the API such as lseek(HOLE), 197 * which detect 'hole' of a sparse file. 198 */ 199 200 static int 201 is_sparse_supported(const char *path) 202 { 203 (void)path; /* UNUSED */ 204 return (0); 205 } 206 207 #endif 208 209 /* 210 * Create a sparse file on POSIX like system. 211 */ 212 213 static void 214 create_sparse_file(const char *path, const struct sparse *s) 215 { 216 char buff[1024]; 217 int fd; 218 219 memset(buff, ' ', sizeof(buff)); 220 assert((fd = open(path, O_CREAT | O_WRONLY, 0600)) != -1); 221 while (s->type != END) { 222 if (s->type == HOLE) { 223 assert(lseek(fd, s->size, SEEK_CUR) != (off_t)-1); 224 } else { 225 size_t w, size; 226 227 size = s->size; 228 while (size) { 229 if (size > sizeof(buff)) 230 w = sizeof(buff); 231 else 232 w = size; 233 assert(write(fd, buff, w) != (ssize_t)-1); 234 size -= w; 235 } 236 } 237 s++; 238 } 239 close(fd); 240 } 241 242 #endif 243 244 /* 245 * Sparse test with directory traversals. 246 */ 247 static void 248 verify_sparse_file(struct archive *a, const char *path, 249 const struct sparse *sparse, int blocks) 250 { 251 struct archive_entry *ae; 252 const void *buff; 253 size_t bytes_read; 254 int64_t offset; 255 int64_t total; 256 int data_blocks, hole; 257 258 create_sparse_file(path, sparse); 259 assert((ae = archive_entry_new()) != NULL); 260 assertEqualIntA(a, ARCHIVE_OK, archive_read_disk_open(a, path)); 261 assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header2(a, ae)); 262 /* Verify the number of holes only, not its offset nor its 263 * length because those alignments are deeply dependence on 264 * its filesystem. */ 265 assertEqualInt(blocks, archive_entry_sparse_count(ae)); 266 total = 0; 267 data_blocks = 0; 268 hole = 0; 269 while (ARCHIVE_OK == archive_read_data_block(a, &buff, &bytes_read, 270 &offset)) { 271 if (offset > total || offset == 0) { 272 if (offset > total) 273 hole = 1; 274 data_blocks++; 275 } 276 total = offset + bytes_read; 277 } 278 if (!hole && data_blocks == 1) 279 data_blocks = 0;/* There are no holes */ 280 assertEqualInt(blocks, data_blocks); 281 282 assertEqualIntA(a, ARCHIVE_OK, archive_read_close(a)); 283 archive_entry_free(ae); 284 } 285 286 #if defined(_WIN32) && !defined(__CYGWIN__) 287 #define close _close 288 #define open _open 289 #endif 290 291 /* 292 * Sparse test without directory traversals. 293 */ 294 static void 295 verify_sparse_file2(struct archive *a, const char *path, 296 const struct sparse *sparse, int blocks, int preopen) 297 { 298 struct archive_entry *ae; 299 int fd; 300 301 (void)sparse; /* UNUSED */ 302 assert((ae = archive_entry_new()) != NULL); 303 archive_entry_set_pathname(ae, path); 304 if (preopen) 305 fd = open(path, O_RDONLY | O_BINARY); 306 else 307 fd = -1; 308 assertEqualIntA(a, ARCHIVE_OK, 309 archive_read_disk_entry_from_file(a, ae, fd, NULL)); 310 if (fd >= 0) 311 close(fd); 312 /* Verify the number of holes only, not its offset nor its 313 * length because those alignments are deeply dependence on 314 * its filesystem. */ 315 assertEqualInt(blocks, archive_entry_sparse_count(ae)); 316 archive_entry_free(ae); 317 } 318 319 static void 320 test_sparse_whole_file_data() 321 { 322 struct archive_entry *ae; 323 int64_t offset; 324 int i; 325 326 assert((ae = archive_entry_new()) != NULL); 327 archive_entry_set_size(ae, 1024*10); 328 329 /* 330 * Add sparse block data up to the file size. 331 */ 332 offset = 0; 333 for (i = 0; i < 10; i++) { 334 archive_entry_sparse_add_entry(ae, offset, 1024); 335 offset += 1024; 336 } 337 338 failure("There should be no sparse"); 339 assertEqualInt(0, archive_entry_sparse_count(ae)); 340 archive_entry_free(ae); 341 } 342 343 DEFINE_TEST(test_sparse_basic) 344 { 345 char *cwd; 346 struct archive *a; 347 /* 348 * The alignment of the hole of sparse files deeply depends 349 * on filesystem. In my experience, sparse_file2 test with 350 * 204800 bytes hole size did not pass on ZFS and the result 351 * of that test seemed the size was too small, thus you should 352 * keep a hole size more than 409600 bytes to pass this test 353 * on all platform. 354 */ 355 const struct sparse sparse_file0[] = { 356 { DATA, 1024 }, { HOLE, 2048000 }, 357 { DATA, 2048 }, { HOLE, 2048000 }, 358 { DATA, 4096 }, { HOLE, 20480000 }, 359 { DATA, 8192 }, { HOLE, 204800000 }, 360 { DATA, 1 }, { END, 0 } 361 }; 362 const struct sparse sparse_file1[] = { 363 { HOLE, 409600 }, { DATA, 1 }, 364 { HOLE, 409600 }, { DATA, 1 }, 365 { HOLE, 409600 }, { END, 0 } 366 }; 367 const struct sparse sparse_file2[] = { 368 { HOLE, 409600 * 1 }, { DATA, 1024 }, 369 { HOLE, 409600 * 2 }, { DATA, 1024 }, 370 { HOLE, 409600 * 3 }, { DATA, 1024 }, 371 { HOLE, 409600 * 4 }, { DATA, 1024 }, 372 { HOLE, 409600 * 5 }, { DATA, 1024 }, 373 { HOLE, 409600 * 6 }, { DATA, 1024 }, 374 { HOLE, 409600 * 7 }, { DATA, 1024 }, 375 { HOLE, 409600 * 8 }, { DATA, 1024 }, 376 { HOLE, 409600 * 9 }, { DATA, 1024 }, 377 { HOLE, 409600 * 10}, { DATA, 1024 },/* 10 */ 378 { HOLE, 409600 * 1 }, { DATA, 1024 * 1 }, 379 { HOLE, 409600 * 2 }, { DATA, 1024 * 2 }, 380 { HOLE, 409600 * 3 }, { DATA, 1024 * 3 }, 381 { HOLE, 409600 * 4 }, { DATA, 1024 * 4 }, 382 { HOLE, 409600 * 5 }, { DATA, 1024 * 5 }, 383 { HOLE, 409600 * 6 }, { DATA, 1024 * 6 }, 384 { HOLE, 409600 * 7 }, { DATA, 1024 * 7 }, 385 { HOLE, 409600 * 8 }, { DATA, 1024 * 8 }, 386 { HOLE, 409600 * 9 }, { DATA, 1024 * 9 }, 387 { HOLE, 409600 * 10}, { DATA, 1024 * 10},/* 20 */ 388 { END, 0 } 389 }; 390 const struct sparse sparse_file3[] = { 391 /* This hole size is too small to create a sparse 392 * files for almost filesystem. */ 393 { HOLE, 1024 }, { DATA, 10240 }, 394 { END, 0 } 395 }; 396 397 /* 398 * Test for the case that sparse data indicates just the whole file 399 * data. 400 */ 401 test_sparse_whole_file_data(); 402 403 /* Check if the filesystem where CWD on can 404 * report the number of the holes of a sparse file. */ 405 #ifdef PATH_MAX 406 cwd = getcwd(NULL, PATH_MAX);/* Solaris getcwd needs the size. */ 407 #else 408 cwd = getcwd(NULL, 0); 409 #endif 410 if (!assert(cwd != NULL)) 411 return; 412 if (!is_sparse_supported(cwd)) { 413 free(cwd); 414 skipping("This filesystem or platform do not support " 415 "the reporting of the holes of a sparse file through " 416 "API such as lseek(HOLE)"); 417 return; 418 } 419 420 /* 421 * Get sparse data through directory traversals. 422 */ 423 assert((a = archive_read_disk_new()) != NULL); 424 425 verify_sparse_file(a, "file0", sparse_file0, 5); 426 verify_sparse_file(a, "file1", sparse_file1, 2); 427 verify_sparse_file(a, "file2", sparse_file2, 20); 428 verify_sparse_file(a, "file3", sparse_file3, 0); 429 430 assertEqualInt(ARCHIVE_OK, archive_read_free(a)); 431 432 /* 433 * Get sparse data through archive_read_disk_entry_from_file(). 434 */ 435 assert((a = archive_read_disk_new()) != NULL); 436 437 verify_sparse_file2(a, "file0", sparse_file0, 5, 0); 438 verify_sparse_file2(a, "file0", sparse_file0, 5, 1); 439 440 assertEqualInt(ARCHIVE_OK, archive_read_free(a)); 441 free(cwd); 442 } 443