sparseutils

utilities for interacting with sparse files
git clone git://git.vx21.xyz/sparseutils
Log | Files | Refs | README | LICENSE

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()