1#!/usr/bin/env python3 2 3""" 4Utility for inspecting files in various ways. 5 6This tool is like the collection of tools found in a unix environment but are 7cross platform and stable and suitable for our needs in the test suite. 8 9This can be used instead of tools like: 10 [ 11 dd 12 find 13 head 14 hexdump 15 ls 16 md5sum 17 readlink 18 sha1sum 19 stat 20 tail 21 test 22 readlink.py 23 md5sum.py 24""" 25 26from __future__ import absolute_import 27 28import binascii 29import glob 30import hashlib 31import optparse 32import os 33import re 34import sys 35 36# Python 3 adapters 37ispy3 = sys.version_info[0] >= 3 38if ispy3: 39 40 def iterbytes(s): 41 for i in range(len(s)): 42 yield s[i : i + 1] 43 44 45else: 46 iterbytes = iter 47 48 49def visit(opts, filenames, outfile): 50 """Process filenames in the way specified in opts, writing output to 51 outfile.""" 52 for f in sorted(filenames): 53 isstdin = f == '-' 54 if not isstdin and not os.path.lexists(f): 55 outfile.write(b'%s: file not found\n' % f.encode('utf-8')) 56 continue 57 quiet = opts.quiet and not opts.recurse or isstdin 58 isdir = os.path.isdir(f) 59 islink = os.path.islink(f) 60 isfile = os.path.isfile(f) and not islink 61 dirfiles = None 62 content = None 63 facts = [] 64 if isfile: 65 if opts.type: 66 facts.append(b'file') 67 if any((opts.hexdump, opts.dump, opts.md5, opts.sha1, opts.sha256)): 68 with open(f, 'rb') as fobj: 69 content = fobj.read() 70 elif islink: 71 if opts.type: 72 facts.append(b'link') 73 content = os.readlink(f).encode('utf8') 74 elif isstdin: 75 content = getattr(sys.stdin, 'buffer', sys.stdin).read() 76 if opts.size: 77 facts.append(b'size=%d' % len(content)) 78 elif isdir: 79 if opts.recurse or opts.type: 80 dirfiles = glob.glob(f + '/*') 81 facts.append(b'directory with %d files' % len(dirfiles)) 82 elif opts.type: 83 facts.append(b'type unknown') 84 if not isstdin: 85 stat = os.lstat(f) 86 if opts.size and not isdir: 87 facts.append(b'size=%d' % stat.st_size) 88 if opts.mode and not islink: 89 facts.append(b'mode=%o' % (stat.st_mode & 0o777)) 90 if opts.links: 91 facts.append(b'links=%d' % stat.st_nlink) 92 if opts.newer: 93 # mtime might be in whole seconds so newer file might be same 94 if stat.st_mtime >= os.stat(opts.newer).st_mtime: 95 facts.append( 96 b'newer than %s' % opts.newer.encode('utf8', 'replace') 97 ) 98 else: 99 facts.append( 100 b'older than %s' % opts.newer.encode('utf8', 'replace') 101 ) 102 if opts.md5 and content is not None: 103 h = hashlib.md5(content) 104 facts.append(b'md5=%s' % binascii.hexlify(h.digest())[: opts.bytes]) 105 if opts.sha1 and content is not None: 106 h = hashlib.sha1(content) 107 facts.append( 108 b'sha1=%s' % binascii.hexlify(h.digest())[: opts.bytes] 109 ) 110 if opts.sha256 and content is not None: 111 h = hashlib.sha256(content) 112 facts.append( 113 b'sha256=%s' % binascii.hexlify(h.digest())[: opts.bytes] 114 ) 115 if isstdin: 116 outfile.write(b', '.join(facts) + b'\n') 117 elif facts: 118 outfile.write(b'%s: %s\n' % (f.encode('utf-8'), b', '.join(facts))) 119 elif not quiet: 120 outfile.write(b'%s:\n' % f.encode('utf-8')) 121 if content is not None: 122 chunk = content 123 if not islink: 124 if opts.lines: 125 if opts.lines >= 0: 126 chunk = b''.join(chunk.splitlines(True)[: opts.lines]) 127 else: 128 chunk = b''.join(chunk.splitlines(True)[opts.lines :]) 129 if opts.bytes: 130 if opts.bytes >= 0: 131 chunk = chunk[: opts.bytes] 132 else: 133 chunk = chunk[opts.bytes :] 134 if opts.hexdump: 135 for i in range(0, len(chunk), 16): 136 s = chunk[i : i + 16] 137 outfile.write( 138 b'%04x: %-47s |%s|\n' 139 % ( 140 i, 141 b' '.join(b'%02x' % ord(c) for c in iterbytes(s)), 142 re.sub(b'[^ -~]', b'.', s), 143 ) 144 ) 145 if opts.dump: 146 if not quiet: 147 outfile.write(b'>>>\n') 148 outfile.write(chunk) 149 if not quiet: 150 if chunk.endswith(b'\n'): 151 outfile.write(b'<<<\n') 152 else: 153 outfile.write(b'\n<<< no trailing newline\n') 154 if opts.recurse and dirfiles: 155 assert not isstdin 156 visit(opts, dirfiles, outfile) 157 158 159if __name__ == "__main__": 160 parser = optparse.OptionParser("%prog [options] [filenames]") 161 parser.add_option( 162 "-t", 163 "--type", 164 action="store_true", 165 help="show file type (file or directory)", 166 ) 167 parser.add_option( 168 "-m", "--mode", action="store_true", help="show file mode" 169 ) 170 parser.add_option( 171 "-l", "--links", action="store_true", help="show number of links" 172 ) 173 parser.add_option( 174 "-s", "--size", action="store_true", help="show size of file" 175 ) 176 parser.add_option( 177 "-n", "--newer", action="store", help="check if file is newer (or same)" 178 ) 179 parser.add_option( 180 "-r", "--recurse", action="store_true", help="recurse into directories" 181 ) 182 parser.add_option( 183 "-S", 184 "--sha1", 185 action="store_true", 186 help="show sha1 hash of the content", 187 ) 188 parser.add_option( 189 "", 190 "--sha256", 191 action="store_true", 192 help="show sha256 hash of the content", 193 ) 194 parser.add_option( 195 "-M", "--md5", action="store_true", help="show md5 hash of the content" 196 ) 197 parser.add_option( 198 "-D", "--dump", action="store_true", help="dump file content" 199 ) 200 parser.add_option( 201 "-H", "--hexdump", action="store_true", help="hexdump file content" 202 ) 203 parser.add_option( 204 "-B", "--bytes", type="int", help="number of characters to dump" 205 ) 206 parser.add_option( 207 "-L", "--lines", type="int", help="number of lines to dump" 208 ) 209 parser.add_option( 210 "-q", "--quiet", action="store_true", help="no default output" 211 ) 212 (opts, filenames) = parser.parse_args(sys.argv[1:]) 213 if not filenames: 214 filenames = ['-'] 215 216 visit(opts, filenames, getattr(sys.stdout, 'buffer', sys.stdout)) 217