sparsemap.py (3052B)
1 # sparsemap 2 # 3 # Copyright © 2017 - 2022 Richard Ipsum 4 # 5 # This program is free software: you can redistribute it and/or modify 6 # it under the terms of the GNU General Public License as published by 7 # the Free Software Foundation, either version 3 of the License, or 8 # (at your option) any later version. 9 # 10 # This program is distributed in the hope that it will be useful, 11 # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 # GNU General Public License for more details. 14 # 15 # You should have received a copy of the GNU General Public License 16 # along with this program. If not, see <http://www.gnu.org/licenses/>. 17 # 18 # =*= License: GPL-3+ =*= 19 20 import sys 21 import os 22 import stat 23 import errno 24 import argparse 25 26 DESCRIPTION = '''Print the structure of a sparse file to stdout. 27 28 The file is interpreted as a sequence of data and holes, for example 29 given a file with 8192 bytes of data followed by a 4096 byte hole followed 30 by 8192 bytes of data the output of sparsemap would be: 31 32 DATA 8192 33 HOLE 4096 34 DATA 8192 35 36 ''' 37 38 def sparsemap(fd): 39 40 # First of all, where are we currently, data or hole? 41 end_of_file_pos = os.lseek(fd, 0, os.SEEK_END) 42 what = os.SEEK_DATA 43 pos = os.lseek(fd, 0, os.SEEK_HOLE) 44 45 if pos == 0: 46 what = os.SEEK_DATA # we are already in a hole 47 elif pos == end_of_file_pos: 48 # no holes in this file 49 print('DATA', end_of_file_pos) 50 return 51 else: 52 what = os.SEEK_HOLE # we were in data 53 pos = 0 54 55 while pos < end_of_file_pos: 56 57 current = 'DATA' if what == os.SEEK_HOLE else 'HOLE' 58 59 try: 60 next_pos = os.lseek(fd, pos, what) 61 except OSError as e: 62 if e.errno == errno.ENXIO: 63 # whatever we were looking for isn't in the file 64 # that means that either the rest of the file is a hole or data 65 print(current, end_of_file_pos - pos) 66 return 67 68 print(current, next_pos - pos) 69 70 pos = next_pos 71 what = os.SEEK_DATA if what == os.SEEK_HOLE else os.SEEK_HOLE 72 73 def main(): 74 if not hasattr(os, 'SEEK_HOLE'): 75 msgfmt = "{}: error: platform does not provide SEEK_HOLE" 76 print(msgfmt.format(sys.argv[0]), file=sys.stderr) 77 sys.exit(1) 78 79 parser = argparse.ArgumentParser( 80 description=DESCRIPTION, 81 formatter_class=argparse.RawDescriptionHelpFormatter) 82 parser.add_argument('FILE') 83 84 args = vars(parser.parse_args()) 85 path = args['FILE'] 86 87 try: 88 mode = os.stat(path).st_mode 89 except os.error as e: 90 print("{}: Couldn't open `{}': {}".format(sys.argv[0], path, e.strerror), 91 file=sys.stderr) 92 sys.exit(1) 93 94 if not stat.S_ISREG(mode): 95 print("{}: error: `{}' is not a regular file".format(sys.argv[0], path), 96 file=sys.stderr) 97 sys.exit(1) 98 99 fd = os.open(path, os.O_RDONLY) 100 sparsemap(fd) 101 os.close(fd) 102 103 if __name__ == '__main__': 104 main()