| 183 | |
| 184 | |
| 185 | def test_sparse_file(archivers, request): |
| 186 | archiver = request.getfixturevalue(archivers) |
| 187 | |
| 188 | def is_sparse(fn, total_size, hole_size): |
| 189 | st = os.stat(fn) |
| 190 | assert st.st_size == total_size |
| 191 | sparse = True |
| 192 | if sparse and hasattr(st, "st_blocks") and st.st_blocks * 512 >= st.st_size: |
| 193 | sparse = False |
| 194 | if sparse and has_seek_hole: |
| 195 | with open(fn, "rb") as fd: |
| 196 | # only check if the first hole is as expected, because the 2nd hole check |
| 197 | # is problematic on xfs due to its "dynamic speculative EOF pre-allocation |
| 198 | try: |
| 199 | if fd.seek(0, os.SEEK_HOLE) != 0: |
| 200 | sparse = False |
| 201 | if fd.seek(0, os.SEEK_DATA) != hole_size: |
| 202 | sparse = False |
| 203 | except OSError: |
| 204 | # OS/FS does not really support SEEK_HOLE/SEEK_DATA |
| 205 | sparse = False |
| 206 | return sparse |
| 207 | |
| 208 | filename_in = os.path.join(archiver.input_path, "sparse") |
| 209 | content = b"foobar" |
| 210 | hole_size = 5 * (1 << CHUNK_MAX_EXP) # 5 full chunker buffers |
| 211 | total_size = hole_size + len(content) + hole_size |
| 212 | with open(filename_in, "wb") as fd: |
| 213 | # create a file that has a hole at the beginning and end (if the |
| 214 | # OS and filesystem supports sparse files) |
| 215 | fd.seek(hole_size, 1) |
| 216 | fd.write(content) |
| 217 | fd.seek(hole_size, 1) |
| 218 | pos = fd.tell() |
| 219 | fd.truncate(pos) |
| 220 | # we first check if we could create a sparse input file: |
| 221 | sparse_support = is_sparse(filename_in, total_size, hole_size) |
| 222 | if sparse_support: |
| 223 | # we could create a sparse input file, so creating a backup of it and |
| 224 | # extracting it again (as sparse) should also work: |
| 225 | cmd(archiver, "repo-create", RK_ENCRYPTION) |
| 226 | cmd(archiver, "create", "test", "input") |
| 227 | with changedir(archiver.output_path): |
| 228 | cmd(archiver, "extract", "test", "--sparse") |
| 229 | assert_dirs_equal("input", "output/input") |
| 230 | filename_out = os.path.join(archiver.output_path, "input", "sparse") |
| 231 | with open(filename_out, "rb") as fd: |
| 232 | # check if file contents are as expected |
| 233 | assert fd.read(hole_size) == b"\0" * hole_size |
| 234 | assert fd.read(len(content)) == content |
| 235 | assert fd.read(hole_size) == b"\0" * hole_size |
| 236 | assert is_sparse(filename_out, total_size, hole_size) |
| 237 | os.unlink(filename_out) # save space on TMPDIR |
| 238 | os.unlink(filename_in) # save space on TMPDIR |
| 239 | |
| 240 | |
| 241 | def test_unusual_filenames(archivers, request): |