Skip to content

Instantly share code, notes, and snippets.

@jkortus
Forked from jimparis/punch.py
Last active August 29, 2015 14:15
Show Gist options
  • Save jkortus/e56296e337ea3527b345 to your computer and use it in GitHub Desktop.
Save jkortus/e56296e337ea3527b345 to your computer and use it in GitHub Desktop.
#!/usr/bin/python
import ctypes
import ctypes.util
c_off_t = ctypes.c_int64
def make_fallocate():
libc_name = ctypes.util.find_library('c')
libc = ctypes.CDLL(libc_name)
_fallocate = libc.fallocate
_fallocate.restype = ctypes.c_int
_fallocate.argtypes = [ctypes.c_int, ctypes.c_int, c_off_t, c_off_t]
del libc
del libc_name
def fallocate(fd, mode, offset, len_):
res = _fallocate(fd.fileno(), mode, offset, len_)
if res != 0:
raise IOError(res, 'fallocate')
return fallocate
fallocate = make_fallocate()
del make_fallocate
FALLOC_FL_KEEP_SIZE = 0x01
FALLOC_FL_PUNCH_HOLE = 0x02
def sizeof_fmt(num, suffix='B'):
for unit in ['','Ki','Mi','Gi','Ti','Pi','Ei','Zi']:
if abs(num) < 1024.0:
return "%3.0f %s%s" % (num, unit, suffix)
num /= 1024.0
return "%.1f%s%s" % (num, 'Yi', suffix)
def punch(filename, verbose, dry_run, print_summary):
blocksize = 4096
saved_space = 0
extents = 0
if verbose:
print "processing", filename
with open(filename, 'r+') as f:
offset = 0
length = 0
last_block_was_hole = False
while True:
buf = f.read(blocksize)
if not buf:
break
if buf == "\x00" * blocksize:
if verbose:
print "punching hole at offset", offset, "length", len(buf)
if not dry_run:
fallocate(f, FALLOC_FL_KEEP_SIZE | FALLOC_FL_PUNCH_HOLE,
offset, len(buf))
saved_space += len(buf)
if not last_block_was_hole:
extents += 1
last_block_was_hole = True
else:
last_block_was_hole = False
offset = offset + blocksize
if print_summary:
print "Saved space: %s" % sizeof_fmt(saved_space)
print "Extents created: %s" % extents
if __name__ == '__main__':
import sys
import argparse
parser = argparse.ArgumentParser(
description = "Punch out the empty areas in a file, making it sparse")
parser.add_argument('file', metavar='FILE',
help='file(s) to modify in-place', nargs='+')
parser.add_argument('-s', '--summary', action="store_true", default=False,
help="print summary of extents created and space saved")
parser.add_argument('-n', '--dry-run', action="store_true", default=False,
help="do not actually modify the file")
parser.add_argument('-v', '--verbose', action="store_true", default=False,
help='be verbose')
args = parser.parse_args()
for filename in args.file:
punch(filename, args.verbose, args.dry_run, args.summary)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment