| 1 | # |
| 2 | # python_pathfix - adjust python shebang paths |
| 3 | # |
| 4 | |
| 5 | from __future__ import print_function |
| 6 | from __future__ import unicode_literals |
| 7 | import sys |
| 8 | import os |
| 9 | from stat import ST_MODE |
| 10 | import getopt |
| 11 | |
| 12 | def main(): |
| 13 | |
| 14 | # Pick up default interpreter (ours) |
| 15 | python_interpreter = os.path.normcase(sys.executable).encode() |
| 16 | |
| 17 | # Don't bother reporting |
| 18 | verbose = False |
| 19 | |
| 20 | # Set default return code |
| 21 | ret = 0 |
| 22 | |
| 23 | usage = ('usage: %s -v -i /python_interpreter file-or-directory ...\n' % sys.argv[0]) |
| 24 | |
| 25 | try: |
| 26 | opts, args = getopt.getopt(sys.argv[1:], 'i:v') |
| 27 | except getopt.GetoptError as msg: |
| 28 | sys.stderr.write(str(msg) + '\n') |
| 29 | sys.stderr.write(usage) |
| 30 | sys.exit(1) |
| 31 | for o, a in opts: |
| 32 | if o == '-i': |
| 33 | python_interpreter = a.encode() |
| 34 | if o == '-v': |
| 35 | verbose = True |
| 36 | |
| 37 | if not python_interpreter or not python_interpreter.startswith(b'/'): |
| 38 | sys.stderr.write('-i interpreter option invalid (must be specified as a full path)\n') |
| 39 | sys.stderr.write(usage) |
| 40 | sys.exit(1) |
| 41 | |
| 42 | if not args: |
| 43 | sys.stderr.write('file or directories not specified\n') |
| 44 | sys.stderr.write(usage) |
| 45 | sys.exit(1) |
| 46 | |
| 47 | for arg in args: |
| 48 | if os.path.islink(arg): |
| 49 | continue |
| 50 | if os.path.isfile(arg): |
| 51 | if pathfix(filename=arg, |
| 52 | verbose=verbose, |
| 53 | python_interpreter=python_interpreter): |
| 54 | ret = 1 |
| 55 | if os.path.isdir(arg): |
| 56 | for root, subdirs, files in os.walk(arg): |
| 57 | for f in files: |
| 58 | filename = os.path.join(root, f) |
| 59 | if os.path.islink(filename): |
| 60 | continue |
| 61 | if os.path.isfile(filename): |
| 62 | if pathfix(filename=filename, |
| 63 | verbose=verbose, |
| 64 | python_interpreter=python_interpreter): |
| 65 | ret = 1 |
| 66 | |
| 67 | sys.exit(ret) |
| 68 | |
| 69 | def pathfix(filename=None, verbose=False, python_interpreter='/usr/bin/python'): |
| 70 | if not filename: |
| 71 | if verbose: |
| 72 | sys.stdout.write('filename not provided\n') |
| 73 | return 1 |
| 74 | # open input file |
| 75 | try: |
| 76 | infile = open(filename, 'rb') |
| 77 | except IOError as msg: |
| 78 | sys.stderr.write('%s: open failed: %r\n' % (filename, msg)) |
| 79 | return 1 |
| 80 | # process first line |
| 81 | try: |
| 82 | firstline = infile.readline() |
| 83 | except IOError as msg: |
| 84 | sys.stderr.write('%s: read failed: %r\n' % (filename, msg)) |
| 85 | try: |
| 86 | infile.close() |
| 87 | except IOError as msg: |
| 88 | sys.stderr.write('%s: close failed: %r\n' % (filename, msg)) |
| 89 | return 1 |
| 90 | if firstline.rstrip(b'\n').find(b' -') != -1: |
| 91 | existingargs = firstline.rstrip(b'\n')[firstline.rstrip(b'\n').find(b' -'):] |
| 92 | else: |
| 93 | existingargs = b'' |
| 94 | newline = b'#!' + python_interpreter + existingargs + b'\n' |
| 95 | if (not firstline.startswith(b'#!')) or (b"python" not in firstline) or (firstline == newline): |
| 96 | if verbose: |
| 97 | sys.stdout.write('%s: unchanged\n' % (filename)) |
| 98 | try: |
| 99 | infile.close() |
| 100 | except IOError as msg: |
| 101 | sys.stderr.write('%s: close failed: %r\n' % (filename, msg)) |
| 102 | return 0 |
| 103 | # create temporary output |
| 104 | head, tail = os.path.split(filename) |
| 105 | tempname = os.path.join(head, '@' + tail) |
| 106 | try: |
| 107 | outfile = open(tempname, 'wb') |
| 108 | except IOError as msg: |
| 109 | sys.stderr.write('%s: create failed: %r\n' % (tempname, msg)) |
| 110 | try: |
| 111 | infile.close() |
| 112 | except IOError as msg: |
| 113 | sys.stderr.write('%s: close failed: %r\n' % (filename, msg)) |
| 114 | return 1 |
| 115 | if verbose: |
| 116 | sys.stdout.write('%s: updating\n' % (filename)) |
| 117 | # write first new line to temporary output |
| 118 | try: |
| 119 | outfile.write(newline) |
| 120 | except IOError as msg: |
| 121 | sys.stderr.write('%s: write failed: %r\n' % (tempname, msg)) |
| 122 | try: |
| 123 | infile.close() |
| 124 | except IOError as msg: |
| 125 | sys.stderr.write('%s: close failed: %r\n' % (filename, msg)) |
| 126 | try: |
| 127 | outfile.close() |
| 128 | except IOError as msg: |
| 129 | sys.stderr.write('%s: close failed: %r\n' % (tempname, msg)) |
| 130 | return 1 |
| 131 | # copy rest of file |
| 132 | while True: |
| 133 | try: |
| 134 | chunk = infile.read(32678) |
| 135 | except IOError as msg: |
| 136 | sys.stderr.write('%s: read failed: %r\n' % (filename, msg)) |
| 137 | try: |
| 138 | infile.close() |
| 139 | except IOError as msg: |
| 140 | sys.stderr.write('%s: close failed: %r\n' % (filename, msg)) |
| 141 | try: |
| 142 | outfile.close() |
| 143 | except IOError as msg: |
| 144 | sys.stderr.write('%s: close failed: %r\n' % (tempname, msg)) |
| 145 | try: |
| 146 | os.remove(tempname) |
| 147 | except OSError as msg: |
| 148 | sys.stderr.write('%s: remove failed: %r\n' % (tempname, msg)) |
| 149 | return 1 |
| 150 | if not chunk: |
| 151 | break |
| 152 | try: |
| 153 | outfile.write(chunk) |
| 154 | except IOError as msg: |
| 155 | sys.stderr.write('%s: write failed: %r\n' % (tempname, msg)) |
| 156 | try: |
| 157 | infile.close() |
| 158 | except IOError as msg: |
| 159 | sys.stderr.write('%s: close failed: %r\n' % (filename, msg)) |
| 160 | try: |
| 161 | outfile.close() |
| 162 | except IOError as msg: |
| 163 | sys.stderr.write('%s: close failed: %r\n' % (tempname, msg)) |
| 164 | try: |
| 165 | os.remove(tempname) |
| 166 | except OSError as msg: |
| 167 | sys.stderr.write('%s: remove failed: %r\n' % (tempname, msg)) |
| 168 | return 1 |
| 169 | # close files |
| 170 | try: |
| 171 | infile.close() |
| 172 | except IOError as msg: |
| 173 | sys.stderr.write('%s: close failed: %r\n' % (filename, msg)) |
| 174 | try: |
| 175 | outfile.close() |
| 176 | except IOError as msg: |
| 177 | sys.stderr.write('%s: close failed: %r\n' % (tempname, msg)) |
| 178 | try: |
| 179 | os.remove(tempname) |
| 180 | except OSError as msg: |
| 181 | sys.stderr.write('%s: remove failed: %r\n' % (tempname, msg)) |
| 182 | return 1 |
| 183 | try: |
| 184 | outfile.close() |
| 185 | except IOError as msg: |
| 186 | sys.stderr.write('%s: close failed: %r\n' % (tempname, msg)) |
| 187 | try: |
| 188 | os.remove(tempname) |
| 189 | except OSError as msg: |
| 190 | sys.stderr.write('%s: remove failed: %r\n' % (tempname, msg)) |
| 191 | return 1 |
| 192 | # copy the file's mode to the temp file |
| 193 | try: |
| 194 | statbuf = os.stat(filename) |
| 195 | os.chmod(tempname, statbuf[ST_MODE] & 0o7777) |
| 196 | except OSError as msg: |
| 197 | sys.stderr.write('%s: warning, chmod failed: %r\n' % (tempname, msg)) |
| 198 | # Remove original file |
| 199 | try: |
| 200 | os.remove(filename) |
| 201 | except OSError as msg: |
| 202 | sys.stderr.write('%s: remove failed: %r\n' % (filename, msg)) |
| 203 | try: |
| 204 | os.remove(tempname) |
| 205 | except OSError as msg: |
| 206 | sys.stderr.write('%s: remove failed: %r\n' % (tempname, msg)) |
| 207 | return 1 |
| 208 | # move the temp file to the original file |
| 209 | try: |
| 210 | os.rename(tempname, filename) |
| 211 | except OSError as msg: |
| 212 | sys.stderr.write('%s: rename failed: %r\n' % (filename, msg)) |
| 213 | # leave the tempname alone (might be used for recovery) |
| 214 | return 1 |
| 215 | return 0 |
| 216 | |
| 217 | if __name__ == '__main__': |
| 218 | main() |