Attachment 'fslinstaller.py'

Download

   1 #!/usr/bin/python
   2 
   3 from __future__ import print_function
   4 
   5 import collections
   6 import csv
   7 import errno
   8 import getpass
   9 import itertools
  10 import json
  11 import locale
  12 import os
  13 import platform
  14 import threading
  15 import time
  16 import shlex
  17 import socket
  18 import sys
  19 import readline
  20 import tempfile
  21 import re
  22 import fileinput
  23 
  24 # py3
  25 try:
  26     import urllib.request as urlrequest
  27     import urllib.error   as urlerror
  28 
  29 # py2
  30 except ImportError:
  31     import urllib2 as urlrequest
  32     import urllib2 as urlerror
  33 
  34 from optparse import OptionParser, OptionGroup, SUPPRESS_HELP
  35 from re import compile, escape, sub
  36 from subprocess import Popen, call, PIPE, STDOUT
  37 
  38 try:
  39     from subprocess import DEVNULL  # py3
  40 except ImportError:
  41     DEVNULL = open(os.devnull, 'wb')
  42 
  43 locale.setlocale(locale.LC_ALL, '')
  44 code = locale.getpreferredencoding()
  45 
  46 PYVER = sys.version_info[:2]
  47 
  48 
  49 fsli_C_FAILED = 1
  50 fsli_C_OK = 2
  51 fsli_C_SKIP = 4
  52 fsli_C_WARN = 3
  53 CURRENT = 0
  54 UPDATE = 1
  55 UPGRADE = 2
  56 BOURNE_SHELLS = ('sh', 'bash', 'zsh', 'ksh', 'dash', )
  57 C_SHELLS = ('csh', 'tcsh', )
  58 
  59 
  60 class Version(object):
  61     def __init__(self, version_string):
  62         if ':' in version_string:
  63             version_string = version_string.split(':')[0]
  64         v_vals = version_string.split('.')
  65 
  66         for v in v_vals:
  67             if not v.isdigit():
  68                 raise ValueError('Bad version string')
  69         self.major = int(v_vals[0])
  70         try:
  71             self.minor = int(v_vals[1])
  72         except IndexError:
  73             self.minor = 0
  74         try:
  75             self.patch = int(v_vals[2])
  76         except IndexError:
  77             self.patch = 0
  78         try:
  79             self.hotfix = int(v_vals[3])
  80         except IndexError:
  81             self.hotfix = 0
  82 
  83     def __repr__(self):
  84         return "Version(%s,%s,%s,%s)" % (
  85             self.major,
  86             self.minor,
  87             self.patch,
  88             self.hotfix)
  89 
  90     def __str__(self):
  91         if self.hotfix == 0:
  92             return "%s.%s.%s" % (self.major, self.minor, self.patch)
  93         else:
  94             return "%s.%s.%s.%s" % (
  95                 self.major,
  96                 self.minor,
  97                 self.patch,
  98                 self.hotfix)
  99 
 100     def __ge__(self, other):
 101         if not isinstance(other, Version):
 102             return NotImplemented
 103         if self > other or self == other:
 104             return True
 105         return False
 106 
 107     def __le__(self, other):
 108         if not isinstance(other, Version):
 109             return NotImplemented
 110         if self < other or self == other:
 111             return True
 112         return False
 113 
 114     def __cmp__(self, other):
 115         if not isinstance(other, Version):
 116             return NotImplemented
 117         if self.__lt__(other):
 118             return -1
 119         if self.__gt__(other):
 120             return 1
 121         return 0
 122 
 123     def __lt__(self, other):
 124         if not isinstance(other, Version):
 125             return NotImplemented
 126         if self.major < other.major:
 127             return True
 128         if self.major > other.major:
 129             return False
 130         if self.minor < other.minor:
 131             return True
 132         if self.minor > other.minor:
 133             return False
 134         if self.patch < other.patch:
 135             return True
 136         if self.patch > other.patch:
 137             return False
 138         if self.hotfix < other.hotfix:
 139             return True
 140         if self.hotfix > other.hotfix:
 141             return False
 142         # major, minor and patch all match so this is not less than
 143         return False
 144 
 145     def __gt__(self, other):
 146         if not isinstance(other, Version):
 147             return NotImplemented
 148         if self.major > other.major:
 149             return True
 150         if self.major < other.major:
 151             return False
 152         if self.minor > other.minor:
 153             return True
 154         if self.minor < other.minor:
 155             return False
 156         if self.patch > other.patch:
 157             return True
 158         if self.patch < other.patch:
 159             return False
 160         if self.hotfix > other.hotfix:
 161             return True
 162         if self.hotfix < other.hotfix:
 163             return False
 164         # major, minor and patch all match so this is not less than
 165         return False
 166 
 167     def __eq__(self, other):
 168         if not isinstance(other, Version):
 169             return NotImplemented
 170         if (
 171                 self.major == other.major and
 172                 self.minor == other.minor and
 173                 self.patch == other.patch and
 174                 self.hotfix == other.hotfix):
 175             return True
 176         return False
 177 
 178     def __ne__(self, other):
 179         if not isinstance(other, Version):
 180             return NotImplemented
 181         if self.__eq__(other):
 182             return False
 183         return True
 184 
 185 
 186 version = Version('3.2.3')
 187 
 188 
 189 def memoize(f):
 190     cache = f.cache = {}
 191 
 192     def g(*args, **kwargs):
 193         key = (f, tuple(args), frozenset(list(kwargs.items())))
 194         if key not in cache:
 195             cache[key] = f(*args, **kwargs)
 196         return cache[key]
 197     return g
 198 
 199 
 200 class InstallError(Exception):
 201     pass
 202 
 203 
 204 class shell_colours(object):
 205     default = '\033[0m'
 206     rfg_kbg = '\033[91m'
 207     gfg_kbg = '\033[92m'
 208     yfg_kbg = '\033[93m'
 209     mfg_kbg = '\033[95m'
 210     yfg_bbg = '\033[104;93m'
 211     bfg_kbg = '\033[34m'
 212     bold = '\033[1m'
 213 
 214 
 215 class MsgUser(object):
 216     __debug = False
 217     __quiet = False
 218 
 219     @classmethod
 220     def debugOn(cls):
 221         cls.__debug = True
 222 
 223     @classmethod
 224     def debugOff(cls):
 225         cls.__debug = False
 226 
 227     @classmethod
 228     def quietOn(cls):
 229         cls.__quiet = True
 230 
 231     @classmethod
 232     def quietOff(cls):
 233         cls.__quiet = False
 234 
 235     @classmethod
 236     def isquiet(cls):
 237         return cls.__quiet
 238 
 239     @classmethod
 240     def isdebug(cls):
 241         return cls.__debug
 242 
 243     @classmethod
 244     def debug(cls, message, newline=True):
 245         if cls.__debug:
 246             mess = str(message)
 247             if newline:
 248                 mess += "\n"
 249             sys.stderr.write(mess)
 250 
 251     @classmethod
 252     def message(cls, msg):
 253         if cls.__quiet:
 254             return
 255         print(msg)
 256 
 257     @classmethod
 258     def question(cls, msg):
 259         print(msg, end=' ')
 260 
 261     @classmethod
 262     def skipped(cls, msg):
 263         if cls.__quiet:
 264             return
 265         print("".join(
 266             (shell_colours.mfg_kbg, "[Skipped] ", shell_colours.default, msg)))
 267 
 268     @classmethod
 269     def ok(cls, msg):
 270         if cls.__quiet:
 271             return
 272         print("".join(
 273             (shell_colours.gfg_kbg, "[OK] ", shell_colours.default, msg)))
 274 
 275     @classmethod
 276     def failed(cls, msg):
 277         print("".join(
 278             (shell_colours.rfg_kbg, "[FAILED] ", shell_colours.default, msg)))
 279 
 280     @classmethod
 281     def warning(cls, msg):
 282         if cls.__quiet:
 283             return
 284         print("".join(
 285             (shell_colours.bfg_kbg,
 286              shell_colours.bold,
 287              "[Warning]",
 288              shell_colours.default, " ", msg)))
 289 
 290 
 291 class Progress_bar(object):
 292     def __init__(self, x=0, y=0, mx=1, numeric=False, percentage=False):
 293         self.x = x
 294         self.y = y
 295         self.width = 50
 296         self.current = 0
 297         self.max = mx
 298         self.numeric = numeric
 299         self.percentage = percentage
 300 
 301     def update(self, reading):
 302         if MsgUser.isquiet():
 303             return
 304         percent = int(round(reading * 100.0 / self.max))
 305         cr = '\r'
 306         if not self.numeric and not self.percentage:
 307             bar = '#' * int(percent)
 308         elif self.numeric:
 309             bar = "/".join(
 310                 (str(reading),
 311                  str(self.max))) + ' - ' + str(percent) + "%\033[K"
 312         elif self.percentage:
 313             bar = "%s%%" % (percent)
 314         sys.stdout.write(cr)
 315         sys.stdout.write(bar)
 316         sys.stdout.flush()
 317         self.current = percent
 318         if percent == 100:
 319             sys.stdout.write(cr)
 320             if not self.numeric and not self.percentage:
 321                 sys.stdout.write(" " * int(percent))
 322                 sys.stdout.write(cr)
 323                 sys.stdout.flush()
 324             elif self.numeric:
 325                 sys.stdout.write(" " * (len(str(self.max))*2 + 8))
 326                 sys.stdout.write(cr)
 327                 sys.stdout.flush()
 328             elif self.percentage:
 329                 sys.stdout.write("100%")
 330                 sys.stdout.write(cr)
 331                 sys.stdout.flush()
 332 
 333 
 334 def temp_file_name(mode='r', close=False):
 335     '''Return a name for a temporary file - uses mkstemp to create the file and
 336     returns a tuple (file object, file name).
 337     Opens as read-only unless mode specifies otherwise. If close is set to True
 338     will close the file before returning.
 339     The file object is a fdopen file object so lacks a useable file name.'''
 340     (tmpfile, fname) = tempfile.mkstemp()
 341     file_obj = os.fdopen(tmpfile, mode)
 342 
 343     if close:
 344         file_obj.close()
 345     return (file_obj, fname)
 346 
 347 
 348 class RunCommandError(Exception):
 349     pass
 350 
 351 
 352 class Spinner(object):
 353     spinner = itertools.cycle(('-', '\\', '|', '/', ))
 354     busy = False
 355     delay = 0.2
 356 
 357     def __init__(self, delay=None, quiet=False):
 358         if delay:
 359             try:
 360                 self.delay = float(delay)
 361             except ValueError:
 362                 pass
 363         self.quiet = quiet
 364 
 365     def spin_it(self):
 366         while self.busy:
 367             sys.stdout.write(next(self.spinner))
 368             sys.stdout.flush()
 369             time.sleep(self.delay)
 370             sys.stdout.write('\b')
 371             sys.stdout.flush()
 372 
 373     def start(self):
 374         if not self.quiet:
 375             self.busy = True
 376             threading.Thread(target=self.spin_it).start()
 377 
 378     def stop(self):
 379         self.busy = False
 380         time.sleep(self.delay)
 381 
 382 
 383 def run_cmd_dropstdout(command, as_root=False):
 384     '''Run the command and return result.'''
 385     command_line = shlex.split(command)
 386 
 387     if as_root and os.getuid() != 0:
 388         try:
 389             sudo_pwd = get_sudo_pwd()
 390         except SudoPasswordError:
 391             raise RunCommandError(
 392                 "Unable to get valid administrator's password")
 393         command_line.insert(0, '-S')
 394         command_line.insert(0, 'sudo')
 395     else:
 396         sudo_pwd = ''
 397     try:
 398         my_spinner = Spinner(quiet=MsgUser.isquiet())
 399         my_spinner.start()
 400         cmd = Popen(command_line, stdin=PIPE, stdout=None, stderr=PIPE,
 401                     universal_newlines=True)
 402         if sudo_pwd:
 403             cmd.stdin.write(sudo_pwd + '\n')
 404             cmd.stdin.flush()
 405         (_, error) = cmd.communicate()
 406     except Exception:
 407         raise
 408     finally:
 409         my_spinner.stop()
 410     if cmd.returncode:
 411         MsgUser.debug("An error occured (%s, %s)" % (cmd.returncode, error))
 412         raise RunCommandError(error)
 413 
 414 
 415 def run_cmd(command, as_root=False):
 416     '''Run the command and return result.'''
 417     command_line = shlex.split(command)
 418 
 419     if as_root and os.getuid() != 0:
 420         try:
 421             sudo_pwd = get_sudo_pwd()
 422         except SudoPasswordError:
 423             raise RunCommandError(
 424                 "Unable to get valid administrator's password")
 425         command_line.insert(0, '-S')
 426         command_line.insert(0, 'sudo')
 427     else:
 428         sudo_pwd = ''
 429     MsgUser.debug("Will call %s" % (command_line))
 430     try:
 431         my_spinner = Spinner(quiet=MsgUser.isquiet())
 432         my_spinner.start()
 433         cmd = Popen(command_line, stdin=PIPE, stdout=PIPE, stderr=PIPE,
 434                     universal_newlines=True)
 435         if sudo_pwd:
 436             cmd.stdin.write(sudo_pwd + '\n')
 437             cmd.stdin.flush()
 438         (output, error) = cmd.communicate()
 439     except Exception:
 440         raise
 441     finally:
 442         my_spinner.stop()
 443     if cmd.returncode:
 444         MsgUser.debug("An error occured (%s, %s)" % (cmd.returncode, error))
 445         raise RunCommandError(error)
 446     MsgUser.debug("Command completed successfully (%s)" % (output))
 447     return output
 448 
 449 
 450 def run_cmd_displayoutput(command, as_root=False):
 451     '''Run the command and display output.'''
 452     command_line = shlex.split(command)
 453 
 454     if as_root and os.getuid() != 0:
 455         try:
 456             sudo_pwd = get_sudo_pwd()
 457         except SudoPasswordError:
 458             raise RunCommandError(
 459                 "Unable to get valid administrator's password")
 460 
 461         command_line.insert(0, '-S')
 462         command_line.insert(0, 'sudo')
 463         MsgUser.debug("Will call %s" % (command_line))
 464         cmd = Popen(
 465             command_line,
 466             stdin=PIPE, stdout=sys.stdout, stderr=sys.stderr,
 467             universal_newlines=True)
 468         if sudo_pwd:
 469             cmd.stdin.write(sudo_pwd + '\n')
 470             cmd.stdin.flush()
 471         cmd.communicate()
 472         return_code = cmd.returncode
 473     else:
 474         return_code = call(command_line)
 475 
 476     if return_code:
 477         MsgUser.debug("An error occured (%s)" % (return_code))
 478         raise RunCommandError(return_code)
 479     MsgUser.debug("Command completed successfully")
 480 
 481 
 482 def check_sudo(sudo_pwd):
 483     command_line = ['sudo', '-S', 'true']
 484     MsgUser.debug("Checking sudo password")
 485     cmd = Popen(
 486         command_line,
 487         stdin=PIPE,
 488         stdout=DEVNULL,
 489         stderr=DEVNULL,
 490         universal_newlines=True
 491     )
 492     cmd.stdin.write(sudo_pwd + '\n')
 493     cmd.stdin.flush()
 494     cmd.communicate()
 495 
 496     if cmd.returncode != 0:
 497         return False
 498     else:
 499         return True
 500 
 501 
 502 class SudoPasswordError(Exception):
 503     pass
 504 
 505 
 506 @memoize
 507 def get_sudo_pwd():
 508     '''Get the sudo password from the user'''
 509     MsgUser.message("We require your password to continue...")
 510     attempts = 0
 511     valid = False
 512 
 513     while attempts < 3 and not valid:
 514         sudo_pwd = getpass.getpass('password: ')
 515         valid = check_sudo(sudo_pwd)
 516         if not valid:
 517             MsgUser.failed("Incorrect password")
 518         attempts += 1
 519     if not valid:
 520         raise SudoPasswordError()
 521     return sudo_pwd
 522 
 523 
 524 class DeletionRefused(Exception):
 525     pass
 526 
 527 
 528 class SafeDeleteError(Exception):
 529     pass
 530 
 531 
 532 def safe_delete(fs_object, as_root=False):
 533     '''Delete file/folder, becoming root if necessary.
 534     Run some sanity checks on object'''
 535 
 536     banned_items = ['/', '/usr', '/usr/bin', '/usr/local', '/bin',
 537                     '/sbin', '/opt', '/Library', '/System', '/System/Library',
 538                     '/var', '/tmp', '/var/tmp', '/lib', '/lib64', '/Users',
 539                     '/home', '/Applications', '/private', '/etc', '/dev',
 540                     '/Network', '/net', '/proc']
 541     if os.path.isdir(fs_object):
 542         del_opts = "-rf"
 543     else:
 544         del_opts = '-f'
 545 
 546     if fs_object in banned_items:
 547         raise DeletionRefused('Will not delete %s!' % (fs_object))
 548 
 549     command_line = " ".join(('rm', del_opts, fs_object))
 550     try:
 551         result = run_cmd(command_line, as_root)
 552     except RunCommandError as e:
 553         raise SafeDeleteError(str(e))
 554     return result
 555 
 556 
 557 class MoveError(Exception):
 558     pass
 559 
 560 
 561 def move(source, target, as_root):
 562     try:
 563         run_cmd_dropstdout(" ".join(('mv', source, target)), as_root)
 564     except RunCommandError as e:
 565         raise MoveError(str(e))
 566 
 567 
 568 class IsDirectoryError(Exception):
 569     pass
 570 
 571 
 572 class CopyFileError(Exception):
 573     pass
 574 
 575 
 576 def copy_file(fname, destination, as_root):
 577     '''Copy a file using sudo if necessary'''
 578     MsgUser.debug("Copying %s to %s (as root? %s)" % (
 579         fname, destination, as_root))
 580     if os.path.isdir(fname):
 581         raise IsDirectoryError('Source (%s) is a directory!' % (fname))
 582 
 583     if os.path.isdir(destination):
 584         # Ensure that copying into a folder we have a terminating slash
 585         destination = destination.rstrip('/') + "/"
 586     copy_opts    = '-p'
 587     fname        = '"%s"' % fname
 588     destination  = '"%s"' % destination
 589     command_line = " ".join(('cp', copy_opts, fname, destination))
 590     try:
 591         result = run_cmd(command_line, as_root)
 592     except RunCommandError as e:
 593         raise CopyFileError(str(e))
 594     return result
 595 
 596 
 597 def file_contains(fname, search_for):
 598     '''Equivalent of grep'''
 599     regex = compile(escape(search_for))
 600     found = False
 601     MsgUser.debug("In file_contains.")
 602     MsgUser.debug("Looking for %s in %s." % (search_for, fname))
 603 
 604     f = open(fname, 'r')
 605     for l in f:
 606         if regex.search(l):
 607             found = True
 608             break
 609     f.close()
 610 
 611     return found
 612 
 613 
 614 def file_contains_1stline(fname, search_for):
 615     '''Equivalent of grep - returns first occurrence'''
 616     regex = compile(escape(search_for))
 617     found = ''
 618     MsgUser.debug("In file_contains_1stline.")
 619     MsgUser.debug("Looking for %s in %s." % (search_for, fname))
 620     f = open(fname, 'r')
 621     for l in f:
 622         if regex.search(l):
 623             found = l
 624             break
 625     f.close()
 626 
 627     return found
 628 
 629 
 630 def line_string_replace(line, search_for, replace_with):
 631     return sub(escape(search_for), escape(replace_with), line)
 632 
 633 
 634 def line_starts_replace(line, search_for, replace_with):
 635     if line.startswith(search_for):
 636         return replace_with + '\n'
 637     return line
 638 
 639 
 640 class MoveFileError(Exception):
 641     pass
 642 
 643 
 644 def move_file(from_file, to_file, requires_root=False):
 645     '''Move a file, using /bin/cp via sudo if requested.
 646     Will work around known bugs in python.'''
 647 
 648     if requires_root:
 649         try:
 650             run_cmd_dropstdout(" ".join(
 651                 ("/bin/cp", from_file, to_file)), as_root=True)
 652         except RunCommandError as e:
 653             MsgUser.debug(e)
 654             raise MoveFileError("Failed to move %s (%s)" % (from_file, str(e)))
 655         os.remove(from_file)
 656     else:
 657         try:
 658             move(from_file, to_file, requires_root)
 659         except OSError as e:
 660             # Handle bug in some python versions on OS X writing to NFS home
 661             # folders, Python tries to preserve file flags but NFS can't do
 662             # this. It fails to catch this error and ends up leaving the file
 663             # in the original and new locations!
 664             if e.errno == 45:
 665                 # Check if new file has been created:
 666                 if os.path.isfile(to_file):
 667                     # Check if original exists
 668                     if os.path.isfile(from_file):
 669                         # Destroy original and continue
 670                         os.remove(from_file)
 671                 else:
 672                     try:
 673                         run_cmd_dropstdout("/bin/cp %s %s" % (
 674                                 from_file, to_file), as_root=False)
 675                     except RunCommandError as e:
 676                         MsgUser.debug(e)
 677                         raise MoveFileError("Failed to copy from %s (%s)" % (
 678                                 from_file, str(e)))
 679                     os.remove(from_file)
 680             else:
 681                 raise
 682         except Exception:
 683             raise
 684 
 685 
 686 class EditFileError(Exception):
 687     pass
 688 
 689 
 690 def edit_file(fname, edit_function, search_for, replace_with, requires_root):
 691     '''Search for a simple string in the file given and replace
 692         it with the new text'''
 693     try:
 694         (tmpfile, tmpfname) = temp_file_name(mode='w')
 695         src = open(fname)
 696 
 697         for line in src:
 698             line = edit_function(line, search_for, replace_with)
 699             tmpfile.write(line)
 700         src.close()
 701         tmpfile.close()
 702 
 703         try:
 704             move_file(tmpfname, fname, requires_root)
 705         except MoveFileError as e:
 706             MsgUser.debug(e)
 707             os.remove(tmpfname)
 708             raise EditFileError("Failed to edit %s (%s)" % (fname, str(e)))
 709     except IOError as e:
 710         MsgUser.debug(e.strerror)
 711         raise EditFileError("Failed to edit %s (%s)" % (fname, str(e)))
 712     MsgUser.debug("Modified %s (search %s; replace %s)." % (
 713         fname, search_for, replace_with))
 714 
 715 
 716 class AddToFileError(Exception):
 717     pass
 718 
 719 
 720 def add_to_file(fname, add_lines, requires_root):
 721     '''Add lines to end of a file'''
 722     if isinstance(add_lines, str):
 723         add_lines = add_lines.split('\n')
 724     try:
 725         (tmpfile, tmpfname) = temp_file_name(mode='w')
 726         src = open(fname)
 727 
 728         for line in src:
 729             tmpfile.write(line)
 730         src.close()
 731         tmpfile.write('\n')
 732         for line in add_lines:
 733             tmpfile.write(line)
 734             tmpfile.write('\n')
 735         tmpfile.close()
 736         try:
 737             move_file(tmpfname, fname, requires_root)
 738 
 739         except MoveFileError as e:
 740             os.remove(tmpfname)
 741             MsgUser.debug(e)
 742             raise AddToFileError("Failed to add to file %s (%s)" % (
 743                     fname, str(e)))
 744     except IOError as e:
 745         MsgUser.debug(e.strerror + tmpfname + fname)
 746         raise AddToFileError("Failed to add to file %s" % (fname))
 747     MsgUser.debug("Modified %s (added %s)" % (fname, '\n'.join(add_lines)))
 748 
 749 
 750 class CreateFileError(Exception):
 751     pass
 752 
 753 
 754 def create_file(fname, lines, requires_root):
 755     '''Create a new file containing lines given'''
 756     if isinstance(lines, str):
 757         lines = lines.split('\n')
 758     try:
 759         (tmpfile, tmpfname) = temp_file_name(mode='w')
 760 
 761         for line in lines:
 762             tmpfile.write(line)
 763             tmpfile.write('\n')
 764         tmpfile.close()
 765         try:
 766             move_file(tmpfname, fname, requires_root)
 767         except CreateFileError as e:
 768             os.remove(tmpfname)
 769             MsgUser.debug(e)
 770             raise CreateFileError("Failed to edit %s (%s)" % (fname, str(e)))
 771     except IOError as e:
 772         MsgUser.debug(e.strerror)
 773         raise CreateFileError("Failed to create %s" % (fname))
 774     MsgUser.debug("Created %s (added %s)" % (fname, '\n'.join(lines)))
 775 
 776 
 777 class UnsupportedOs(Exception):
 778     pass
 779 
 780 
 781 class Host(object):
 782     '''Work out which platform we are running on'''
 783     o_s = platform.system().lower()
 784     arch = platform.machine()
 785     applever = ''
 786     os_type = os.name
 787     supported = True
 788 
 789     if o_s == 'darwin':
 790         vendor = 'apple'
 791         version = Version(platform.release())
 792         (applever, _, _) = platform.mac_ver()
 793         glibc = ''
 794     elif o_s == 'linux':
 795         # default to this if we can't detect the linux distro
 796         fallback_vendor = 'centos'
 797         fallback_version = '7.8.2003'
 798 
 799         # python 2.6-3.7 has a linux_distribution function
 800         if hasattr(platform, 'linux_distribution'):
 801             (vendor, version, _) = platform.linux_distribution(
 802                 full_distribution_name=0)
 803         # linux_distributiobn is not present in python >=3.8
 804         else:
 805             vendor, version = fallback_vendor, fallback_version
 806         try:
 807             vendor = vendor.lower()
 808             version = Version(version)
 809         except ValueError:
 810             vendor = fallback_vendor
 811             version = Version(fallback_version)
 812         glibc = platform.libc_ver()[1]
 813     else:
 814         supported = False
 815 
 816     if arch == 'x86_64':
 817         bits = '64'
 818     elif arch == 'i686':
 819         bits = '32'
 820     elif arch == 'Power Macintosh':
 821         bits = ''
 822 
 823 
 824 def is_writeable(location):
 825     '''Check if we can write to the location given'''
 826     writeable = True
 827     try:
 828         tfile = tempfile.NamedTemporaryFile(mode='w+b', dir=location)
 829         tfile.close()
 830     except OSError as e:
 831         if e.errno == errno.EACCES or e.errno == errno.EPERM:
 832             writeable = False
 833         else:
 834             raise
 835     return writeable
 836 
 837 
 838 def is_writeable_as_root(location):
 839     '''Check if sudo can write to a given location'''
 840     # This requires us to use sudo
 841 
 842     (f, fname) = temp_file_name(mode='w')
 843     f.write("FSL")
 844     f.close()
 845 
 846     result = False
 847     tmptarget = '/'.join((location, os.path.basename(fname)))
 848     MsgUser.debug(" ".join(('/bin/cp', fname, tmptarget)))
 849     try:
 850         run_cmd_dropstdout(" ".join(('/bin/cp',
 851                                      fname, tmptarget)), as_root=True)
 852         result = True
 853         os.remove(fname)
 854         run_cmd_dropstdout(" ".join(('/bin/rm',
 855                                      '-f', tmptarget)), as_root=True)
 856     except RunCommandError as e:
 857         MsgUser.debug(e)
 858         os.remove(fname)
 859         result = False
 860     MsgUser.debug("Writeable as root? %s" % (result))
 861     return result
 862 
 863 
 864 class ChecksumCalcError(Exception):
 865     pass
 866 
 867 
 868 def sha256File(filename, bs=1048576):
 869     '''Returns the sha256 sum of the given file.'''
 870     MsgUser.message("Checking FSL package")
 871     try:
 872         import hashlib
 873         f = open(filename, 'rb')
 874         pb = Progress_bar(mx=os.path.getsize(filename), percentage=True)
 875         pb.position = 0
 876         fhash = hashlib.sha256()
 877         data = f.read(bs)
 878         while len(data) == bs:
 879             fhash.update(data)
 880             data = f.read(bs)
 881             pb.position += len(data)
 882             pb.update(pb.position)
 883         fhash.update(data)
 884         f.close()
 885         return fhash.hexdigest()
 886     except ImportError:
 887         # No SHA256 support on python pre-2.5 so call the OS to do it.
 888         try:
 889             result = run_cmd(" ".join(('sha256sum', '-b', filename)))
 890             return parsesha256sumfile(result)
 891         except RunCommandError as e:
 892             MsgUser.debug("SHA256 calculation error %s" % (str(e)))
 893             raise ChecksumCalcError
 894 
 895 
 896 def parsesha256sumfile(sha256string):
 897     '''Returns sha256 sum extracted from the output of sha256sum or shasum -a
 898     256 from OS X/Linux platforms'''
 899     (sha256, _) = sha256string.split("*")
 900     return sha256.strip()
 901 
 902 
 903 def md5File(filename, bs=1048576):
 904     '''Returns the MD5 sum of the given file.'''
 905     MsgUser.message("Checking FSL package")
 906     try:
 907         import hashlib
 908         fhash = hashlib.md5()
 909     except ImportError:
 910         import md5
 911         fhash = md5.new()
 912     f = open(filename, 'rb')
 913     pb = Progress_bar(mx=os.path.getsize(filename), percentage=True)
 914     pb.position = 0
 915     data = f.read(bs)
 916     while len(data) == bs:
 917         fhash.update(data)
 918         data = f.read(bs)
 919         pb.position += len(data)
 920         pb.update(pb.position)
 921     fhash.update(data)
 922     f.close()
 923     return fhash.hexdigest()
 924 
 925 
 926 def file_checksum(filename, chktype='sha256'):
 927     if chktype == 'sha256':
 928         return sha256File(filename)
 929     if chktype == 'md5':
 930         return md5File(filename)
 931     else:
 932         raise ChecksumCalcError('Unrecognised checksum type')
 933 
 934 
 935 class OpenUrlError(Exception):
 936     pass
 937 
 938 
 939 def open_url(url, start=0, timeout=20):
 940     socket.setdefaulttimeout(timeout)
 941     MsgUser.debug("Attempting to download %s." % (url))
 942 
 943     try:
 944         req = urlrequest.Request(url)
 945         if start != 0:
 946             req.headers['Range'] = 'bytes=%s-' % (start)
 947         rf = urlrequest.urlopen(req)
 948     except urlerror.HTTPError as e:
 949         MsgUser.debug("%s %s" % (url, e.msg))
 950         raise OpenUrlError("Cannot find file %s on server (%s). "
 951                            "Try again later." % (url, e.msg))
 952     except urlerror.URLError as e:
 953         if type(e.reason) != str:
 954             errno = e.reason.args[0]
 955             if len(e.reason.args) > 1:
 956                 message = e.reason.args[1]
 957             # give up on trying to identify both the errno and message
 958             else:
 959                 message = e.reason.args
 960             if errno == 8:
 961                 # Bad host name
 962                 MsgUser.debug("%s %s" % (url,
 963                                          "Unable to find FSL download "
 964                                          "server in the DNS"))
 965             else:
 966                 # Other error
 967                 MsgUser.debug("%s %s" % (url, message))
 968         else:
 969             message = str(e.reason)
 970         raise OpenUrlError(
 971             "Cannot find %s (%s). Try again later." % (url, message))
 972     except socket.timeout as e:
 973         MsgUser.debug(e)
 974         raise OpenUrlError("Failed to contact FSL web site. Try again later.")
 975     return rf
 976 
 977 
 978 class DownloadFileError(Exception):
 979     pass
 980 
 981 
 982 def download_file(url, localf, timeout=20):
 983     '''Get a file from the url given storing it in the local file specified'''
 984 
 985     try:
 986         rf = open_url(url, 0, timeout)
 987     except OpenUrlError as e:
 988         raise DownloadFileError(str(e))
 989 
 990     metadata = rf.headers
 991     rf_size = int(metadata.get("Content-Length"))
 992 
 993     dl_size = 0
 994     block = 16384
 995     x = 0
 996     y = 0
 997     pb = Progress_bar(x, y, rf_size, numeric=True)
 998 
 999     for attempt in range(1, 6):
1000         # Attempt download 5 times before giving up
1001         pause = timeout
1002         try:
1003             try:
1004                 lf = open(localf, 'ab')
1005             except Exception:
1006                 raise DownloadFileError("Failed to create temporary file.")
1007 
1008             while True:
1009                 buf = rf.read(block)
1010                 if not buf:
1011                     break
1012                 dl_size += len(buf)
1013                 lf.write(buf)
1014                 pb.update(dl_size)
1015             lf.close()
1016         except (IOError, socket.timeout) as e:
1017             MsgUser.debug(e.strerror)
1018             MsgUser.message("\nDownload failed re-trying (%s)..." % attempt)
1019             pause = 0
1020         if dl_size != rf_size:
1021             time.sleep(pause)
1022             MsgUser.message("\nDownload failed re-trying (%s)..." % attempt)
1023             try:
1024                 rf = open_url(url, dl_size, timeout)
1025             except OpenUrlError as e:
1026                 MsgUser.debug(e)
1027         else:
1028             break
1029     if dl_size != rf_size:
1030         raise DownloadFileError("Failed to download file.")
1031 
1032 
1033 def build_url_with_protocol(protocol, base, parts):
1034     part_l = [protocol + '://' + base.strip('/')]
1035     part_l.extend([x.strip('/') for x in parts])
1036     return '/'.join(part_l)
1037 
1038 
1039 def build_url(parts):
1040     part_l = [parts[0].strip('/')]
1041     part_l.extend([x.strip('/') for x in parts[1:]])
1042     return '/'.join(part_l)
1043 
1044 
1045 class SiteNotResponding(Exception):
1046     pass
1047 
1048 
1049 def fastest_mirror(main_mirrors, mirrors_file, timeout=20):
1050     '''Find the fastest mirror for FSL downloads.'''
1051     MsgUser.debug("Calculating fastest mirror")
1052     socket.setdefaulttimeout(timeout)
1053 
1054     # Get the mirror list from the url
1055     fastestmirrors = {}
1056     mirrorlist = []
1057     for m in main_mirrors:
1058         MsgUser.debug("Trying %s" % (m))
1059         m_url = '/'.join((m.strip('/'), mirrors_file))
1060         MsgUser.debug("Attempting to open %s" % (m_url))
1061         try:
1062             response = urlrequest.urlopen(url=m_url)
1063         except urlerror.HTTPError as e:
1064             MsgUser.debug("%s %s" % (m_url, e.msg))
1065             raise SiteNotResponding(e.msg)
1066         except urlerror.URLError as e:
1067             if isinstance(e.reason, socket.timeout):
1068                 MsgUser.debug("Time out trying %s" % (m_url))
1069                 raise SiteNotResponding(m)
1070             else:
1071                 MsgUser.debug(str(e.reason))
1072                 raise SiteNotResponding(str(e.reason))
1073         except socket.timeout as e:
1074             MsgUser.debug(e)
1075             raise SiteNotResponding(str(e))
1076         except Exception as e:
1077             MsgUser.debug("Unhandled exception %s" % (str(e)))
1078             raise
1079         else:
1080             mirrorlist = response.read().decode('utf-8').strip().split('\n')
1081             MsgUser.debug("Received the following "
1082                           "mirror list %s" % (mirrorlist))
1083             continue
1084 
1085     if len(mirrorlist) == 0:
1086         raise ServerFailure("Cannot find FSL download servers")
1087 
1088     # Check timings from the urls specified
1089     if len(mirrorlist) > 1:
1090         for mirror in mirrorlist:
1091             MsgUser.debug("Trying %s" % (mirror))
1092             then = time.time()
1093             if mirror.startswith('http:'):
1094                 serverport = 80
1095             elif mirror.startswith('https:'):
1096                 serverport = 443
1097             else:
1098                 raise ServerFailure("Unrecognised protocol")
1099             try:
1100                 mysock = socket.create_connection((mirror, serverport),
1101                                                   timeout)
1102                 pingtime = time.time() - then
1103                 mysock.close()
1104                 fastestmirrors[pingtime] = mirror
1105                 MsgUser.debug("Mirror responded in %s seconds" % (pingtime))
1106             except socket.gaierror as e:
1107                 MsgUser.debug("%s can't be resolved" % (e))
1108             except socket.timeout as e:
1109                 MsgUser.debug(e)
1110         if len(fastestmirrors) == 0:
1111             raise ServerFailure('Failed to contact any FSL download sites.')
1112         download_url = fastestmirrors[min(fastestmirrors.keys())]
1113     else:
1114         download_url = mirrorlist[0]
1115 
1116     return download_url
1117 
1118 
1119 # Concept:
1120 # Web app creates the following files:
1121 # fslmirrorlist.txt - contains a list of mirror urls
1122 # fslreleases.json - contains the available maps for oses
1123 #                    mapping to a download url
1124 # {'installer' {
1125 #                'filename': 'fslinstaller.py',
1126 #                'version': '3.0.0',
1127 #                'date': '02/03/2017',
1128 #                'checksum_type', 'sha256',
1129 #                'checksum'},
1130 #  'linux' : {
1131 #               'centos' : {
1132 #                   'x86_64': {
1133 #                       '6': {
1134 #                           '5.0.9': {
1135 #                               'filename': 'fsl-5.0.9-centos6_64.tar.gz',
1136 #                               'version': '5.0.9',
1137 #                               'date': '01/02/2017',
1138 #                               'checksum_type', 'sha256',
1139 #                               'checksum': 'abf645662bcf4453235',
1140 #                               },
1141 #                             },
1142 #                           },
1143 #                         },
1144 #              'rhel' : {'alias': 'centos'}},
1145 #   'apple' : {
1146 #               'darwin' : {
1147 #                   'x86_64': {
1148 #                       '11': {
1149 #                            ....
1150 #             },
1151 # }
1152 
1153 @memoize
1154 def get_web_manifest(download_url, timeout=20):
1155     '''Download the FSL manifest from download_url'''
1156     socket.setdefaulttimeout(timeout)
1157     MsgUser.debug("Looking for manifest at %s." % (download_url))
1158 
1159     MsgUser.debug("Downloading JSON file")
1160     return get_json(download_url + Settings.manifest_json)
1161 
1162 
1163 class GetFslDirError(Exception):
1164     pass
1165 
1166 
1167 @memoize
1168 def get_fsldir(specified_dir=None, install=False):
1169     '''Find the installed version of FSL using FSLDIR
1170     or location of this script'''
1171 
1172     def validate_fsldir(directory):
1173         parent = os.path.dirname(directory)
1174         if parent == directory:
1175             raise GetFslDirError(
1176                 "%s appears to be the root folder" %
1177                 parent)
1178         if not os.path.exists(parent):
1179             raise GetFslDirError(
1180                 "%s doesn't exist" %
1181                 parent)
1182         if not os.path.isdir(parent):
1183             raise GetFslDirError(
1184                 "%s isn't a directory" %
1185                 parent)
1186         if (os.path.exists(directory) and not
1187                 os.path.exists(os.path.join(
1188                         directory, 'etc', 'fslversion'
1189                 ))):
1190             raise GetFslDirError(
1191                 "%s exists and doesn't appear to be an installed FSL folder" %
1192                 directory)
1193 
1194     if specified_dir:
1195         specified_dir = os.path.abspath(specified_dir)
1196         if install is False:
1197             if not check_fsl_install(specified_dir):
1198                 raise GetFslDirError(
1199                         "%s isn't an 'fsl' folder" %
1200                         specified_dir)
1201         else:
1202             validate_fsldir(specified_dir)
1203         return specified_dir
1204     try:
1205         fsldir = os.environ['FSLDIR']
1206         try:
1207             validate_fsldir(fsldir)
1208         except GetFslDirError:
1209             # FSLDIR environment variable is incorrect!
1210             MsgUser.warning('FSLDIR environment variable '
1211                             'does not point at FSL install, ignoring...')
1212             MsgUser.debug('FSLDIR is set to %s - '
1213                           'this folder does not appear to exist' % (fsldir))
1214             fsldir = None
1215         else:
1216             fsldir = fsldir.rstrip('/')
1217             if MsgUser.isquiet():
1218                 return fsldir
1219     except KeyError:
1220         # Look to see if I'm in an FSL install
1221         try:
1222             my_parent = os.path.dirname(
1223                 os.path.dirname(os.path.realpath(__file__)))
1224         except NameError:
1225             # Running in debugger - __file__ not set, assume it's cwd
1226             my_parent = os.path.dirname(
1227                 os.path.dirname(os.getcwd()))
1228         try:
1229             validate_fsldir(my_parent)
1230             fsldir = my_parent
1231         except GetFslDirError:
1232             fsldir = None
1233 
1234     if not install:
1235         MsgUser.debug("asking about %s" % (fsldir))
1236         valid_dir = False
1237         while not valid_dir:
1238             fsldir = Settings.inst_qus.ask_question(
1239                     'inst_loc', default=fsldir)
1240             try:
1241                 validate_fsldir(fsldir)
1242                 valid_dir = True
1243             except GetFslDirError as e:
1244                 MsgUser.falied(str(e))
1245         return fsldir
1246 
1247     else:
1248         if not MsgUser.isquiet():
1249             valid_dir = False
1250             while not valid_dir:
1251                 fsldir = Settings.inst_qus.ask_question(
1252                     'location', default=fsldir)
1253                 try:
1254                     validate_fsldir(fsldir)
1255                     valid_dir = True
1256                 except GetFslDirError as e:
1257                     MsgUser.failed(str(e))
1258                     MsgUser.message(
1259                         '''Hint - press Enter to select the default value '''
1260                         '''given in the square brackets.
1261 If you are specifying a destination folder this needs to either be an existing
1262 FSL install folder or a folder that doesn't already exist.''')
1263                     fsldir = None
1264         else:
1265             raise GetFslDirError(
1266                     "I can't locate FSL, try again using '-d <FSLDIR>' "
1267                     "to specify where to find the FSL install")
1268     return fsldir
1269 
1270 
1271 def archive_version(archive):
1272     '''Takes the path to a FSL install file
1273     and works out what version it is.'''
1274     if not os.path.isfile(archive):
1275         raise NotAFslVersion("%s is not a file" % (archive))
1276     else:
1277         # file is of form: fsl-V.V.V-platform.extensions
1278         (_, vstring, _) = archive.strip().split('-', 2)
1279         try:
1280             return Version(vstring)
1281         except ValueError:
1282             raise NotAFslVersion(
1283                     "%s doesn't look like "
1284                     "a version number" % (vstring))
1285 
1286 
1287 class NotAFslVersion(Exception):
1288     pass
1289 
1290 
1291 class GetInstalledVersionError(Exception):
1292     pass
1293 
1294 
1295 def get_installed_version(fsldir):
1296     '''Takes path to FSLDIR and finds installed version details'''
1297     MsgUser.debug("Looking for fsl in %s" % fsldir)
1298     v_file = os.path.join(fsldir, 'etc', 'fslversion')
1299     if os.path.exists(v_file):
1300         f = open(v_file)
1301         v_string = f.readline()
1302         f.close()
1303         try:
1304             version = Version(v_string.strip())
1305         except ValueError:
1306             raise NotAFslVersion(
1307                     "%s not a valid "
1308                     "version string" % (v_string.strip()))
1309     else:
1310         MsgUser.debug(
1311                 "No version information found - "
1312                 "is this actually an FSL dir?")
1313         raise GetInstalledVersionError(
1314                 "Cannot find the version information - "
1315                 "is this actually an FSL dir?")
1316     MsgUser.debug("Found version %s" % (version))
1317     return version
1318 
1319 
1320 def which_shell():
1321     return os.path.basename(os.getenv("SHELL"))
1322 
1323 
1324 class SelfUpdateError(Exception):
1325     pass
1326 
1327 
1328 def self_update(server_url):
1329     '''Check for and apply an update to myself'''
1330     # See if there is a newer version available
1331     if 'fslinstaller' in sys.argv[0]:
1332         try:
1333             installer = get_installer(server_url)
1334         except GetInstallerError as e:
1335             MsgUser.debug("Failed to get installer version %s." % (str(e)))
1336             raise SelfUpdateError('Failed to get installer version. '
1337                                   'Please try again later.')
1338 
1339         MsgUser.debug("Server has version " + installer['version'])
1340         if Version(installer['version']) <= version:
1341             MsgUser.debug("Installer is up-to-date.")
1342             return
1343         # There is a new version available - download it
1344         MsgUser.message("There is a newer version (%s) of the installer "
1345                         "(you have %s) updating..." % (
1346                             installer['version'], version))
1347         (_, tmpfname) = temp_file_name(mode='w', close=True)
1348 
1349         downloaded = False
1350         while downloaded is False:
1351             try:
1352                 file_url = '/'.join(
1353                     (Settings.mirror.rstrip('/'), installer['filename']))
1354                 download_file(
1355                     url=file_url,
1356                     localf=tmpfname)
1357                 if (
1358                     file_checksum(tmpfname, installer['checksum_type']) !=
1359                         installer['checksum']):
1360                     raise SelfUpdateError(
1361                         "Found update to installer but download "
1362                         "was corrupt. Please try again later.")
1363             except DownloadFileError as e:
1364                 if Settings.mirror != Settings.main_mirror:
1365                     MsgUser.warning(
1366                             "Download from mirror failed, re-trying from "
1367                             "main FSL download site")
1368                     Settings.mirror = Settings.main_mirror
1369                 else:
1370                     MsgUser.debug("Failed to update installer %s." % (str(e)))
1371                     raise SelfUpdateError(
1372                             'Found update to installer but unable to '
1373                             'download the new version. Please try again.')
1374             else:
1375                 downloaded = True
1376         # Now run the new installer
1377         # EXEC new script with the options we were given
1378         os.chmod(tmpfname, 0o755)
1379         c_args = [sys.executable, tmpfname, ]
1380         c_args.extend(sys.argv[1:])
1381         MsgUser.debug(
1382             "Calling %s %s" % (sys.executable, c_args))
1383         os.execv(sys.executable, c_args)
1384     else:
1385         # We are now running the newly downloaded installer
1386         MsgUser.ok('Installer updated to latest version %s' % (str(version)))
1387         MsgUser.ok("Installer self update successful.")
1388 
1389 
1390 class ServerFailure(Exception):
1391     pass
1392 
1393 
1394 class BadVersion(Exception):
1395     pass
1396 
1397 
1398 class GetInstallerError(Exception):
1399     pass
1400 
1401 
1402 def get_installer(server_url):
1403     MsgUser.debug("Checking %s for "
1404                   "installer information" % (server_url))
1405     manifest = get_web_manifest(server_url)
1406     return manifest['installer']
1407 
1408 
1409 @memoize
1410 def get_releases(server_url):
1411     '''Return a hash with all information about available
1412     versions for this OS'''
1413     computer = Host
1414     MsgUser.debug("Getting web manifest")
1415     manifest = get_web_manifest(server_url)
1416     try:
1417         os_definition = manifest[computer.o_s][computer.vendor]
1418     except KeyError:
1419         raise UnsupportedOs("%s %s not supported by this installer" % (
1420             computer.o_s, computer.vendor
1421         ))
1422     t_version = computer.version.major
1423     alias_t = 'alias'
1424     if alias_t in list(os_definition.keys()):
1425         if str(t_version) in os_definition[alias_t]:
1426             os_parent = os_definition[alias_t][
1427                             str(t_version)]['parent']
1428             t_version = os_definition[alias_t][
1429                             str(t_version)]['version']
1430 
1431             os_definition = manifest[computer.o_s][os_parent]
1432 
1433     if computer.arch not in list(os_definition.keys()):
1434         raise UnsupportedOs("%s %s not supported" % (
1435                                 computer.vendor,
1436                                 computer.arch
1437                             ))
1438 
1439     os_def = os_definition[computer.arch]
1440     while t_version > 0:
1441         MsgUser.debug("Trying version %s" % (t_version))
1442         if str(t_version) not in list(os_def.keys()):
1443             MsgUser.debug("...not found")
1444             t_version -= 1
1445         else:
1446             break
1447     if t_version == 0:
1448         raise UnsupportedOs("%s %s not supported" % (
1449                                 computer.vendor,
1450                                 computer.version.major
1451                                 ))
1452     elif t_version != computer.version.major:
1453         MsgUser.warning(
1454                         "%s %s not officially supported "
1455                         "- trying to locate support for an earlier "
1456                         "version - this may not work" % (
1457                                 computer.vendor, computer.version.major))
1458     return os_definition[computer.arch][str(t_version)]
1459 
1460 
1461 class ExtraDownloadError(Exception):
1462     pass
1463 
1464 
1465 @memoize
1466 def get_extra(server_url, extra_type):
1467     '''Return a hash with all information about available
1468     versions of source code'''
1469     MsgUser.debug("Getting web manifest")
1470     manifest = get_web_manifest(server_url)
1471     try:
1472         extra = manifest[extra_type]
1473     except KeyError:
1474         raise ExtraDownloadError("Unrecognised extra %s" % (extra_type))
1475     return extra
1476 
1477 
1478 class ImproperlyConfigured(Exception):
1479     pass
1480 
1481 
1482 def list_releases(url):
1483     releases = get_releases(url)
1484     MsgUser.message("Available FSL versions for this OS:")
1485     MsgUser.debug(releases)
1486 
1487     rels = []
1488 
1489     for v, release in list(releases.items()):
1490         if 'date' in release:
1491             rdate = release['date']
1492         else:
1493             rdate = "Third-party package"
1494         rels.append((v, rdate))
1495 
1496     for v, rdate in sorted(rels, reverse=True):
1497         MsgUser.message("%s\t(%s)" % (v, rdate))
1498 
1499 
1500 def list_builds(url):
1501     '''Lists all available FSL builds. '''
1502     manifest = dict(get_web_manifest(url))
1503 
1504     MsgUser.message("All available FSL builds:")
1505 
1506     centos = manifest['linux']['centos']['x86_64']
1507     macos  = manifest['darwin']['apple']['x86_64']
1508 
1509     def get_platform(s):
1510         match = re.match(r'^fsl-(.+)-(.+).tar.gz$', s)
1511         plat  = match.group(2)
1512         return plat
1513 
1514     fslversions = collections.defaultdict(set)
1515 
1516     for builds in itertools.chain(list(centos.values()), list(macos.values())):
1517         for fslversion, info in list(builds.items()):
1518             fslversions[fslversion].add(get_platform(info['filename']))
1519 
1520     for fslversion, plats in list(fslversions.items()):
1521         MsgUser.message('%s - %s' % (fslversion, ', '.join(plats)))
1522 
1523 
1524 def latest_release(url):
1525     releases = get_releases(url)
1526     MsgUser.debug("Got version information: %s" % (releases))
1527     versions = [Version(x) for x in list(releases.keys())]
1528     MsgUser.debug("Versions: %s" % (versions))
1529     return releases[str(sorted(versions)[-1])]
1530 
1531 
1532 class InstallInstallerError(Exception):
1533     pass
1534 
1535 
1536 def install_installer(fsldir):
1537     '''Install this script into $FSLDIR/etc'''
1538     targetfolder = os.path.join(fsldir, 'etc')
1539     as_root = False
1540     installer = os.path.abspath(__file__)
1541     MsgUser.debug(
1542             "Copying fslinstaller (%s) to %s" % (
1543                     installer,
1544                     targetfolder))
1545     if not is_writeable(targetfolder):
1546         if not is_writeable_as_root(targetfolder):
1547             raise InstallInstallerError("Cannot write to folder as root user.")
1548         else:
1549             as_root = True
1550     copy_file(
1551         installer, os.path.join(targetfolder, "fslinstaller.py"),
1552         as_root)
1553 
1554 
1555 class InstallQuestions(object):
1556     def __init__(self):
1557         self.questions = {}
1558         self.validators = {}
1559         self.preprocs = {}
1560         self.type = {}
1561         self.default = {}
1562         self.defaults = False
1563 
1564     def add_question(self, key, question, default, qtype, validation_f, preproc_f=None):
1565         self.questions[key] = question
1566         self.default[key] = default
1567         self.type[key] = qtype
1568         self.validators[key] = validation_f
1569         self.preprocs[key] = preproc_f
1570 
1571     def ask_question(self, key, default=None):
1572         # Ask a question
1573         no_answer = True
1574         validator = self.validators[key]
1575         preproc = self.preprocs[key]
1576 
1577         def parse_answer(q_type, answer):
1578             if q_type == 'bool':
1579                 if answer.lower() == 'yes':
1580                     return True
1581                 else:
1582                     return False
1583             else:
1584                 return answer
1585 
1586         if not default:
1587             default = self.default[key]
1588 
1589         if self.defaults:
1590             MsgUser.debug(self.questions[key])
1591             MsgUser.debug("Automatically using the default %s" % (default))
1592             self.answers[key] = parse_answer(self.type[key], default)
1593             no_answer = False
1594 
1595         while no_answer:
1596             MsgUser.question(
1597                 "%s? %s:" % (
1598                     self.questions[key],
1599                     '[%s]' % (default)))
1600             if PYVER[0] == 2: your_answer = raw_input()
1601             else:             your_answer = input()
1602 
1603             MsgUser.debug("Your answer was %s" % (your_answer))
1604             if your_answer == '':
1605                 MsgUser.debug("You want the default")
1606                 your_answer = default
1607             elif preproc is not None:
1608                 your_answer = preproc(your_answer)
1609             if validator(your_answer):
1610                 answer = parse_answer(self.type[key], your_answer)
1611                 no_answer = False
1612         MsgUser.debug("Returning the answer %s" % (answer))
1613         return answer
1614 
1615 
1616 def yes_no(answer):
1617     if answer.lower() == 'yes' or answer.lower() == 'no':
1618         return True
1619     else:
1620         MsgUser.message("Please enter yes or no.")
1621     return False
1622 
1623 
1624 def check_install_location(folder):
1625     '''Don't allow relative paths'''
1626     MsgUser.debug("Checking %s is an absolute path" % (folder))
1627     if (folder == '.' or
1628             folder == '..' or
1629             folder.startswith('./') or
1630             folder.startswith('../') or
1631             folder.startswith('~')):
1632         MsgUser.message("Please enter an absolute path.")
1633         return False
1634     return True
1635 
1636 
1637 def external_validate(what_to_check):
1638     '''We will validate elsewhere'''
1639     return True
1640 
1641 
1642 def check_fsl_install(fsldir):
1643     '''Check if this folder contains FSL install'''
1644     MsgUser.debug("Checking %s is an FSL install" % (fsldir))
1645     if os.path.isdir(fsldir):
1646         if os.path.exists(
1647             os.path.join(fsldir, 'etc', 'fslversion')
1648         ):
1649             return True
1650     return False
1651 
1652 
1653 def fsl_downloadname(suffix, version):
1654     return 'fsl-%s-%s' % (
1655             version, suffix)
1656 
1657 
1658 class Settings(object):
1659     version = version
1660     title = "--- FSL Installer - Version %s ---" % (version)
1661     main_server = 'fsl.fmrib.ox.ac.uk'
1662     mirrors = [build_url_with_protocol('https',
1663                                        main_server, ('fsldownloads',
1664                                                      '')), ]
1665     mirrors_file = 'fslmirrorlist.txt'
1666     manifest_json = 'manifest.json'
1667     manifest_csv = 'manifest.csv'
1668     main_mirror = mirrors[0]
1669     mirror = main_mirror
1670 
1671     applications = ['bin/fslview.app', 'bin/assistant.app']
1672     x11 = {'bad_versions': [],
1673            'download_url': "http://xquartz.macosforge.org/landing/",
1674            'apps': ['XQuartz.app', 'X11.app', ],
1675            'location': "/Applications/Utilities"}
1676     default_location = '/usr/local/fsl'
1677     post_inst_dir = "etc/fslconf"
1678 
1679     inst_qus = InstallQuestions()
1680     inst_qus.add_question('version_match',
1681                           "The requested version matches the installed "
1682                           "version - do you wish to re-install FSL",
1683                           'no', 'bool', yes_no)
1684     inst_qus.add_question('location',
1685                           "Where would you like the FSL install to be "
1686                           "(including the FSL folder name)",
1687                           default_location, 'path', check_install_location, os.path.abspath)
1688     inst_qus.add_question('del_old',
1689                           "FSL exists in the current location, "
1690                           "would you like to keep a backup of the old "
1691                           "version (N.B. You will not be able to use the old "
1692                           "version)",
1693                           'no', 'bool', yes_no)
1694     inst_qus.add_question('create',
1695                           "Install location doesn't exist, should I create it",
1696                           'yes', 'bool', yes_no)
1697     inst_qus.add_question('inst_loc',
1698                           "Where is the FSL folder (e.g. /usr/local/fsl)",
1699                           default_location, 'path', check_fsl_install)
1700     inst_qus.add_question('skipmd5',
1701                           "I was unable to download the checksum of "
1702                           "the install file so cannot confirm it is correct. "
1703                           "Would you like to install anyway",
1704                           'no', 'bool', yes_no)
1705     inst_qus.add_question('overwrite',
1706                           "There is already a local copy of the file, would "
1707                           "you like to overwrite it",
1708                           "yes", 'bool', yes_no)
1709     inst_qus.add_question('upgrade',
1710                           "Would you like to install upgrade",
1711                           "yes", 'bool', yes_no)
1712     inst_qus.add_question('update',
1713                           "Would you like to install update",
1714                           "yes", 'bool', yes_no)
1715 
1716 
1717 def get_json(web_url):
1718     MsgUser.debug("Opening "+web_url)
1719     try:
1720         url = open_url(web_url)
1721         data = url.read().decode('utf-8')
1722         return json.loads(data)
1723     except OpenUrlError as e:
1724         raise ServerFailure(str(e))
1725 
1726 
1727 # [ linux, centos, x86_64, 6, filename, 'fname',
1728 #  version, 'version', date, 'date', checksum_type, 'checksum_type',
1729 #  checksum, 'checksum', supported, 'true/false', notes, 'notes',
1730 #  instructions, 'instructions']
1731 # [ linux, redhat, alias, centos, supported, True/false, version, 'version' ]
1732 # [ 'installer', filename, 'fname', version, 'version', date, 'date',
1733 #   checksum_type, 'checksum_type', checksum, 'checksum', supported,
1734 #   'true/false', notes, 'notes', instructions, 'instructions']
1735 # [ feeds, filename, 'fname', version, 'version',
1736 #   date, 'date', checksum_type, 'checksum_type', checksum, 'checksum',
1737 #   supported, 'true/false', notes, 'notes', instructions, 'instructions']
1738 # [ sources, filename, 'fname', version, 'version',
1739 #   date, 'date', checksum_type, 'checksum_type', checksum, 'checksum',
1740 #   supported, 'true/false', notes, 'notes', instructions, 'instructions']
1741 
1742 class AutoDict(dict):
1743     '''Automatically create a nested dict'''
1744     def __getitem__(self, item):
1745         try:
1746             return dict.__getitem__(self, item)
1747         except KeyError:
1748             value = self[item] = type(self)()
1749             return value
1750 
1751     def freeze(self):
1752         '''Returns a dict representation of an AutoDict'''
1753         frozen = {}
1754         for k, v in list(self.items()):
1755             if type(v) == type(self):
1756                 frozen[k] = v.freeze()
1757             else:
1758                 frozen[k] = v
1759         return frozen
1760 
1761 
1762 def get_csv_dict(web_url):
1763     MsgUser.debug("Opening "+web_url)
1764 
1765     try:
1766         url = open_url(web_url)
1767         manifest_reader = csv.reader(
1768             url, delimiter=',', quoting=csv.QUOTE_MINIMAL)
1769         a_dict = AutoDict()
1770         for line in manifest_reader:
1771             MsgUser.debug(line)
1772             if line[0] == 'feeds':
1773                 items = iter(line[1:])
1774                 base_dict = dict(list(zip(items, items)))
1775                 a_dict[line[0]] = base_dict
1776             elif line[0] == 'sources':
1777                 items = iter(line[1:])
1778                 base_dict = dict(list(zip(items, items)))
1779                 a_dict[line[0]] = base_dict
1780             elif line[0] == 'installer':
1781                 items = iter(line[1:])
1782                 base_dict = dict(list(zip(items, items)))
1783                 a_dict[line[0]] = base_dict
1784             else:
1785                 # Install package or alias
1786                 if line[2] == 'alias':
1787                     items = iter(line[4:])
1788                     base_dict = dict(list(zip(items, items)))
1789                     a_dict[
1790                         str(line[0])][
1791                             str(line[1])][
1792                                 str(line[2])][
1793                                     str(line[3])] = base_dict
1794                 else:
1795                     items = iter(line[5:])
1796                     base_dict = dict(list(zip(items, items)))
1797                     MsgUser.debug(
1798                         ",".join(
1799                             (line[0], line[1], line[2], line[3], line[4])))
1800                     a_dict[
1801                         str(line[0])][
1802                             str(line[1])][
1803                                 str(line[2])][
1804                                     str(line[3])][
1805                                         str(line[4])] = base_dict
1806     except OpenUrlError as e:
1807         raise ServerFailure(str(e))
1808     MsgUser.debug(a_dict)
1809     return a_dict.freeze()
1810 
1811 
1812 class InvalidVersion(Exception):
1813     pass
1814 
1815 
1816 def get_web_version_and_details(server_url, request_version=None):
1817     if request_version is None:
1818         details = latest_release(server_url)
1819         try:
1820             version = Version(details['version'])
1821         except KeyError:
1822             try:
1823                 redirect = details['redirect']
1824                 raise DownloadError(
1825                     "Installer not supported on this platform."
1826                     "Please visit %s for download instructions" % redirect)
1827             except KeyError:
1828                 MsgUser.debug(
1829                     "Can't find version or redirect - %s" % details)
1830                 raise DownloadError(
1831                     "Unsupported OS"
1832                 )
1833     else:
1834         MsgUser.debug("Requested version %s" % request_version)
1835         releases = get_releases(server_url)
1836         try:
1837             version = Version(request_version)
1838         except ValueError:
1839             raise DownloadError(
1840                 "%s doesn't look like a version" % request_version)
1841         if request_version not in list(releases.keys()):
1842             raise DownloadError(
1843                 "%s isn't an available version" % request_version)
1844         details = releases[request_version]
1845     return (version, details)
1846 
1847 
1848 def download_release(
1849         server_url, to_temp=False,
1850         request_version=None, skip_verify=False,
1851         keep=False, source_code=False, feeds=False):
1852 
1853     (version, details) = get_web_version_and_details(
1854             server_url, request_version)
1855     if request_version is None:
1856         request_version = str(version)
1857 
1858     if source_code or feeds:
1859         if source_code:
1860             extra_type = 'sources'
1861             MsgUser.message("Downloading source code")
1862         else:
1863             extra_type = 'feeds'
1864             MsgUser.message("Downloading FEEDS")
1865 
1866         try:
1867             releases = get_extra(server_url, extra_type)
1868         except ExtraDownloadError as e:
1869             raise DownloadError(
1870                 "Unable to find details for %s" % (extra_type)
1871             )
1872         to_temp = False
1873         try:
1874             details = releases[request_version]
1875         except KeyError:
1876             raise DownloadError(
1877                 "%s %s isn't available" % (request_version, extra_type)
1878             )
1879 
1880     MsgUser.debug(details)
1881 
1882     if to_temp:
1883         try:
1884             (_, local_filename) = temp_file_name(close=True)
1885         except Exception as e:
1886             MsgUser.debug("Error getting temporary file name %s" % (str(e)))
1887             raise DownloadError("Unable to begin download")
1888     else:
1889         local_filename = details['filename']
1890         if os.path.exists(local_filename):
1891             if os.path.isfile(local_filename):
1892                 MsgUser.message("%s exists" % (local_filename))
1893                 overwrite = Settings.inst_qus.ask_question('overwrite')
1894                 if overwrite:
1895                     MsgUser.warning(
1896                         "Erasing existing file %s" % local_filename)
1897                     try:
1898                         os.remove(local_filename)
1899                     except Exception:
1900                         raise DownloadError(
1901                             "Unabled to remove local file %s - remove"
1902                             " it and try again" % local_filename)
1903                 else:
1904                     raise DownloadError("Aborting download")
1905             else:
1906                 raise DownloadError(
1907                     "There is a directory named %s "
1908                     "- cannot overwrite" % local_filename)
1909 
1910     MsgUser.debug(
1911             "Downloading to file %s "
1912             "(this may take some time)." % (local_filename))
1913     MsgUser.message(
1914             "Downloading...")
1915 
1916     downloaded = False
1917     while downloaded is False:
1918         try:
1919             file_url = '/'.join(
1920                 (Settings.mirror.rstrip('/'), details['filename']))
1921             download_file(
1922                 url=file_url,
1923                 localf=local_filename)
1924             if (not skip_verify and
1925                 (details['checksum'] !=
1926                     file_checksum(local_filename, details['checksum_type']))):
1927                 raise DownloadError('Downloaded file fails checksum')
1928             MsgUser.ok("File downloaded")
1929         except DownloadFileError as e:
1930             MsgUser.debug(str(e))
1931             if Settings.mirror != Settings.main_mirror:
1932                 MsgUser.warning(
1933                         "Download from mirror failed, re-trying from "
1934                         "main FSL download site")
1935                 Settings.mirror = Settings.main_mirror
1936             else:
1937                 raise DownloadError(str(e))
1938         else:
1939             downloaded = True
1940     return (local_filename, version, details)
1941 
1942 
1943 class DownloadError(Exception):
1944     pass
1945 
1946 
1947 def shell_config(shell, fsldir, skip_root=False):
1948     MsgUser.debug("Building environment for %s" % (shell))
1949     env_lines = ''
1950 
1951     if shell in BOURNE_SHELLS:
1952         if skip_root:
1953             env_lines += '''if [ -x /usr/bin/id ]; then
1954   if [ -z "$EUID" ]; then
1955     # ksh and dash doesn't setup the EUID environment var
1956     EUID=`id -u`
1957   fi
1958 fi
1959 if [ "$EUID" != "0" ]; then
1960 '''
1961         env_lines += '''
1962 # FSL Setup
1963 FSLDIR=%s
1964 PATH=${FSLDIR}/bin:${PATH}
1965 export FSLDIR PATH
1966 . ${FSLDIR}/etc/fslconf/fsl.sh
1967 '''
1968         if skip_root:
1969             env_lines += '''fi'''
1970         match = "FSLDIR="
1971         replace = "FSLDIR=%s"
1972     elif shell in C_SHELLS:
1973         if skip_root:
1974             env_lines += '''if ( $uid != 0 ) then
1975 '''
1976         env_lines += '''
1977 # FSL Setup
1978 setenv FSLDIR %s
1979 setenv PATH ${FSLDIR}/bin:${PATH}
1980 source ${FSLDIR}/etc/fslconf/fsl.csh
1981 '''
1982         if skip_root:
1983             env_lines += '''
1984 endif'''
1985         match = "setenv FSLDIR"
1986         replace = "setenv FSLDIR %s"
1987     elif shell == 'matlab':
1988         env_lines = '''
1989 %% FSL Setup
1990 setenv( 'FSLDIR', '%s' );
1991 setenv('FSLOUTPUTTYPE', 'NIFTI_GZ');
1992 fsldir = getenv('FSLDIR');
1993 fsldirmpath = sprintf('%%s/etc/matlab',fsldir);
1994 path(path, fsldirmpath);
1995 clear fsldir fsldirmpath;
1996 '''
1997         match = "setenv( 'FSLDIR',"
1998         replace = "setenv( 'FSLDIR', '%s' );"
1999     else:
2000         raise ValueError("Unknown shell type %s" % shell)
2001     return (env_lines % (fsldir), match, replace % (fsldir))
2002 
2003 
2004 def get_profile(shell):
2005     home = os.path.expanduser("~")
2006 
2007     dotprofile = os.path.join(home, '.profile')
2008     if shell == 'bash':
2009         profile = os.path.join(home, '.bash_profile')
2010         if not os.path.isfile(profile) and os.path.isfile(dotprofile):
2011             profile = dotprofile
2012     elif shell == 'zsh':
2013         profile = os.path.join(home, '.zprofile')
2014         # ZSH will never source .profile
2015     elif shell == 'sh':
2016         profile = dotprofile
2017     else:
2018         cshprofile = os.path.join(home, '.cshrc')
2019         if shell == 'csh':
2020             profile = cshprofile
2021         elif shell == 'tcsh':
2022             profile = os.path.join(home, '.tcshrc')
2023             if not os.path.isfile(profile) and os.path.isfile(cshprofile):
2024                 profile = cshprofile
2025         else:
2026             raise ValueError("Unsupported shell")
2027     return profile
2028 
2029 
2030 class FixFslDirError(Exception):
2031     pass
2032 
2033 
2034 def fix_fsldir(shell, fsldir):
2035     (_, match, replace) = shell_config(shell, fsldir)
2036     profile = get_profile(shell)
2037     MsgUser.debug(
2038             "Editing %s, replacing line beginning:%s with %s." %
2039             (profile, match, replace))
2040     try:
2041         edit_file(profile, line_starts_replace, match, replace, False)
2042     except EditFileError as e:
2043         raise FixFslDirError(str(e))
2044 
2045 
2046 class AddFslDirError(Exception):
2047     pass
2048 
2049 
2050 def add_fsldir(shell, fsldir):
2051     (env_lines, _, _) = shell_config(shell, fsldir)
2052     profile = get_profile(shell)
2053     MsgUser.debug("Adding %s to %s" % (env_lines, profile))
2054     try:
2055         add_to_file(profile, env_lines, False)
2056     except AddToFileError as e:
2057         raise AddFslDirError(str(e))
2058 
2059 
2060 class ConfigureMatlabError(Exception):
2061     pass
2062 
2063 
2064 class ConfigureMatlabWarn(Exception):
2065     pass
2066 
2067 
2068 def configure_matlab(fsldir, m_startup='', c_file=True):
2069     '''Setup your startup.m file to enable FSL MATLAB functions to work'''
2070     (mlines, match, replace) = shell_config('matlab', fsldir)
2071     if m_startup == '':
2072         m_startup = os.path.join(
2073             os.path.expanduser('~'), 'Documents', 'MATLAB', 'startup.m')
2074     if os.path.exists(m_startup):
2075         # Check if already configured
2076         MsgUser.debug("Looking for %s in %s" % (match, m_startup))
2077         if file_contains(m_startup, match):
2078             try:
2079                 MsgUser.debug('Updating MATLAB startup file.')
2080                 edit_file(
2081                     m_startup, line_starts_replace,
2082                     match, replace, False)
2083             except EditFileError as e:
2084                 raise ConfigureMatlabError(str(e))
2085         else:
2086             MsgUser.debug('Adding FSL settings to MATLAB.')
2087             try:
2088                 add_to_file(m_startup, mlines, False)
2089             except AddToFileError as e:
2090                 raise ConfigureMatlabError(str(e))
2091     elif c_file:
2092         # No startup.m file found. Create one
2093         try:
2094             MsgUser.debug('No MATLAB startup.m file found, creating one.')
2095             if not os.path.isdir(os.path.dirname(m_startup)):
2096                 MsgUser.debug('No MATLAB startup.m file found, creating one.')
2097                 os.mkdir(os.path.dirname(m_startup))
2098             create_file(m_startup, mlines, False)
2099         except (OSError, CreateFileError) as e:
2100             MsgUser.debug(
2101                     'Unable to create ~/Documents/MATLAB/ folder or startup.m file,'
2102                     ' cannot configure (%).' % (str(e)))
2103             raise ConfigureMatlabError(
2104                     "Unable to create your ~/Documents/MATLAB/ folder or startup.m, "
2105                     "so cannot configure MATLAB for FSL.")
2106     else:
2107         MsgUser.debug('MATLAB may not be installed, doing nothing.')
2108         raise ConfigureMatlabWarn("I can't tell if you have MATLAB installed.")
2109 
2110 
2111 class SetupEnvironmentError(Exception):
2112     pass
2113 
2114 
2115 class SetupEnvironmentSkip(Exception):
2116     pass
2117 
2118 
2119 def setup_system_environment(fsldir):
2120     '''Add a system-wide profile setting up FSL for all users.
2121     Only supported on Redhat/Centos'''
2122     profile_d = '/etc/profile.d'
2123     profile_files = ['fsl.sh', 'fsl.csh']
2124     exceptions = []
2125     skips = []
2126 
2127     if os.getuid() != 0:
2128         sudo = True
2129     else:
2130         sudo = False
2131 
2132     if os.path.isdir(profile_d):
2133         for profile in profile_files:
2134             pf = profile.split('.')[1]
2135             (lines, match, replace) = shell_config(pf, fsldir)
2136             this_profile = os.path.join(profile_d, profile)
2137             if os.path.exists(this_profile):
2138                 # Already has a profile file
2139                 # Does it contain an exact match for current FSLDIR?
2140                 match = file_contains_1stline(this_profile, replace)
2141                 if match != '':
2142                     # If there is an fsl.(c)sh then just fix
2143                     # the entry for FSLDIR
2144                     MsgUser.debug(
2145                             "Fixing %s for FSLDIR location." % (this_profile))
2146                     try:
2147                         edit_file(
2148                                 this_profile, line_starts_replace,
2149                                 match, replace, sudo)
2150                     except EditFileError as e:
2151                         exceptions.append(str(e))
2152                 else:
2153                     # No need to do anything
2154                     MsgUser.debug(
2155                             "%s already configured - skipping." %
2156                             (this_profile))
2157                     skips.append(profile)
2158             else:
2159                 # Create the file
2160                 try:
2161                     create_file(this_profile, lines, sudo)
2162                 except CreateFileError as e:
2163                     exceptions.append(str(e))
2164 
2165     else:
2166         raise SetupEnvironmentError(
2167             "No system-wide configuration folder found - Skipped")
2168     if exceptions:
2169         raise SetupEnvironmentError(".".join(exceptions))
2170     if skips:
2171         raise SetupEnvironmentSkip(".".join(skips))
2172 
2173 
2174 def setup_environment(fsldir=None, system=False, with_matlab=False):
2175     '''Setup the user's environment so that their
2176     terminal finds the FSL tools etc.'''
2177     # Check for presence of profile file:
2178     if fsldir is None:
2179         fsldir = get_fsldir()
2180 
2181     user_shell = which_shell()
2182     MsgUser.debug("User's shell is %s" % (user_shell))
2183     try:
2184         (profile_lines, _, _) = shell_config(user_shell, fsldir)
2185         profile = get_profile(user_shell)
2186     except ValueError as e:
2187         raise SetupEnvironmentError(str(e))
2188 
2189     cfile = False
2190     if not os.path.isfile(profile):
2191         MsgUser.debug("User is missing a shell setup file.")
2192         cfile = True
2193 
2194     if cfile:
2195         MsgUser.debug("Creating file %s" % (profile))
2196         try:
2197             create_file(profile, profile_lines, False)
2198         except CreateFileError as e:
2199             raise SetupEnvironmentError(
2200                     "Unable to create profile %s" % (profile))
2201     else:
2202         # Check if user already has FSLDIR set
2203         MsgUser.message("Setting up FSL software...")
2204         try:
2205             if file_contains(profile, "FSLDIR"):
2206                 MsgUser.debug("Updating FSLDIR entry.")
2207                 fix_fsldir(user_shell, fsldir)
2208             else:
2209                 MsgUser.debug("Adding FSLDIR entry.")
2210                 add_fsldir(user_shell, fsldir)
2211         except (AddFslDirError, FixFslDirError) as e:
2212             raise SetupEnvironmentError(
2213                     "Unable to update your profile %s"
2214                     " with FSL settings" % (profile))
2215 
2216     if with_matlab:
2217         MsgUser.debug("Setting up MATLAB")
2218         try:
2219             configure_matlab(fsldir)
2220         except ConfigureMatlabError as e:
2221             MsgUser.debug(str(e))
2222             raise SetupEnvironmentError(str(e))
2223         except ConfigureMatlabWarn as e:
2224             MsgUser.skipped(str(e))
2225 
2226 
2227 class PostInstallError(Exception):
2228     pass
2229 
2230 
2231 class InstallArchiveError(Exception):
2232     pass
2233 
2234 
2235 class UnknownArchiveType(Exception):
2236     pass
2237 
2238 
2239 def archive_type(archive):
2240     '''Determine file type based on extension and check
2241     that file looks like this file type'''
2242     archive_types = {
2243         'gzip': ('tar', '-z'),
2244         'bzip2': ('tar', '-j'),
2245         'zip': ('zip', ''), }
2246 
2247     try:
2248         file_type = run_cmd("file %s" % (archive))
2249     except RunCommandError as e:
2250         raise UnknownArchiveType(str(e))
2251     file_type = file_type.lower()
2252     for f_type in ('gzip', 'bzip2', 'zip', ):
2253         if f_type in file_type:
2254             return archive_types[f_type]
2255     raise UnknownArchiveType(archive)
2256 
2257 
2258 def asl_gui_604_patch(fsldir, as_root=False):
2259     '''
2260     fsl 6.0.4 shipped with a broken fsleyes preview in asl_gui.
2261 
2262     This function applies the simple patch to any new installation
2263     that downloads FSL 6.0.4 using the fslinstaller.
2264 
2265     1. parse fsl version
2266 
2267     2. if version == 6.0.4 apply asl_gui patch, else do nothing and return
2268 
2269     to test this patch with an existing fsl 6.0.4:
2270 
2271     1. make a minimal $FSLDIR folder structure
2272         - cd ~
2273         - mkdir fsl_test
2274         - cd fsl_test
2275         - mkdir fsl
2276         - cp -r $FSLDIR/etc fsl/
2277         - cp -r $FSLDIR/python fsl/
2278         - mkdir fsl/bin
2279 
2280     2. tar it up
2281         - tar -czf fsl-6.0.4-centos7_64.tar.gz fsl
2282         - rm -r fsl # remove the fsl folder after tar-ing
2283 
2284     3. run a test python install from the tar file
2285         - be sure to use python 2.X (e.g. 2.7 works fine)
2286         - python fslinstaller.py -f ~/fsl_test/fsl-6.0.4-centos7_64.tar.gz -d ~/fsl_test/fsl -p -M -D
2287     '''
2288     asl_file = os.path.join(fsldir, 'python', 'oxford_asl', 'gui', 'preview_fsleyes.py') #$FSLDIR/python/oxford_asl/gui/preview_fsleyes.py
2289     vfile = os.path.join(fsldir, 'etc', 'fslversion')
2290     vstring = ''
2291     with open(vfile, 'r') as f:
2292         vstring = f.readline()
2293     v = vstring.split(':')[0] # e.g. 6.0.4:wkj2w3jh
2294     if v == '6.0.4':
2295         MsgUser.message("Patching asl_gui for fsl 6.0.4")
2296         tfile = os.path.join(tempfile.mkdtemp(), "preview_fsleyes.py")
2297         # backup asl_file
2298         run_cmd_displayoutput('cp {} {}.bkup'.format(asl_file, asl_file), as_root=as_root)
2299         # copy asl_file to tempfile
2300         run_cmd_displayoutput('cp {} {}'.format(asl_file, tfile), as_root=as_root)
2301         # ensure script can open temp file
2302         run_cmd_displayoutput('chmod 775 {}'.format(tfile), as_root=as_root)
2303 
2304         for line in fileinput.input(files=tfile, inplace=True):
2305             line = re.sub('parent=parent, ready=ready', 'ready=ready, raiseErrors=True', line.rstrip())
2306             print(line)
2307 
2308         run_cmd_displayoutput('cp {} {}'.format(tfile, asl_file), as_root=as_root)
2309         os.remove(tfile)
2310 
2311 
2312 def post_install(
2313         fsldir, settings, script="post_install.sh", quiet=False,
2314         app_links=False, x11=False):
2315     MsgUser.message("Performing post install tasks")
2316     if is_writeable(fsldir):
2317         as_root = False
2318     elif is_writeable_as_root(fsldir):
2319         as_root = True
2320     else:
2321         raise PostInstallError(
2322                 "Unable to write to target folder (%s)" % (fsldir))
2323     install_installer(fsldir)
2324 
2325     # apply asl_gui patch if fsl 6.0.4
2326     asl_gui_604_patch(fsldir, as_root=as_root)
2327     script_path = os.path.join(fsldir, Settings.post_inst_dir, script)
2328     if x11:
2329         try:
2330             check_X11(settings.x11)
2331         except CheckX11Warning as e:
2332             MsgUser.warning(str(e))
2333         else:
2334             MsgUser.ok("X11 (required for GUIs) found")
2335 
2336     if os.path.exists(script_path):
2337         MsgUser.debug("Found post-install script %s" % (script_path))
2338         if not os.access(script_path, os.X_OK):
2339             raise PostInstallError(
2340                 "Unable to run post install script %s" % (script_path)
2341             )
2342         script_opts = '-f "%s"' % (fsldir)
2343         if quiet:
2344             script_opts += " -q"
2345 
2346         command_line = " ".join((script_path, script_opts))
2347         try:
2348             run_cmd_displayoutput(command_line, as_root=as_root)
2349         except RunCommandError as e:
2350             raise PostInstallError(
2351                 "Error running post installation script (error %s)"
2352                 " - check the install log" % (str(e))
2353             )
2354         # Work around for mistake in 5.0.10 post setup script
2355         mal = os.path.join(
2356                     fsldir, Settings.post_inst_dir,
2357                     'make_applications_links.sh')
2358         if (os.path.exists(mal) and
2359                 not file_contains(script_path, "make_applications_links.sh")):
2360             MsgUser.debug(
2361                 "Work around necessary for missing app link creation")
2362         else:
2363             app_links = False
2364     if app_links:
2365         try:
2366             make_applications_links(fsldir, settings.applications)
2367         except MakeApplicationLinksError as e:
2368             for message in list(e.app_messages.values()):
2369                 MsgUser.warning(message)
2370         else:
2371             MsgUser.ok("/Applications links created/updated")
2372 
2373     MsgUser.ok("Post installation setup complete")
2374 
2375 
2376 def install_archive(archive, fsldir=None):
2377     def clean_up_temp():
2378         try:
2379             safe_delete(tempfolder,  as_root)
2380         except SafeDeleteError as sd_e:
2381             MsgUser.debug(
2382                     "Unable to clean up temporary folder! "
2383                     "%s" % (str(sd_e)))
2384     if not os.path.isfile(archive):
2385         raise InstallError("%s isn't a file" % (archive))
2386     if not fsldir:
2387         try:
2388             fsldir = get_fsldir(specified_dir=fsldir, install=True)
2389         except GetFslDirError as e:
2390             raise InstallError(str(e))
2391 
2392     MsgUser.debug("Requested install of %s as %s" % (archive, fsldir))
2393     if os.path.exists(fsldir):
2394         # move old one out of way
2395         MsgUser.debug("FSL version already installed")
2396         keep_old = Settings.inst_qus.ask_question('del_old')
2397     else:
2398         keep_old = False
2399 
2400     install_d = os.path.dirname(fsldir)
2401     MsgUser.debug("Checking %s is writeable." % (install_d))
2402     if is_writeable(install_d):
2403         as_root = False
2404     elif is_writeable_as_root(install_d):
2405         as_root = True
2406     else:
2407         raise InstallArchiveError(
2408                 "Unable to write to target folder (%s), "
2409                 "even as a super user." % (install_d))
2410     MsgUser.debug("Does %s require root for deletion? %s" % (
2411             install_d, as_root))
2412     try:
2413         unarchive, ua_option = archive_type(archive)
2414     except UnknownArchiveType as e:
2415         raise InstallArchiveError(str(e))
2416     # Generate a temporary name - eg fsl-<mypid>-date
2417     tempname = '-'.join(('fsl', str(os.getpid()), str(time.time())))
2418     tempfolder = os.path.join(install_d, tempname)
2419     try:
2420         run_cmd_dropstdout("mkdir %s" % (tempfolder), as_root=as_root)
2421     except RunCommandError as e:
2422         raise InstallArchiveError(
2423                 "Unable to create folder to install into.")
2424     MsgUser.debug(
2425             "Unpacking %s into folder %s." % (archive, tempfolder))
2426     try:
2427         if unarchive == 'tar':
2428             unpack_cmd = 'tar -C %s -x %s -o -f %s' % (
2429                 tempfolder, ua_option, archive)
2430         elif unarchive == 'zip':
2431             MsgUser.debug(
2432                 "Calling unzip %s %s" % (ua_option, archive)
2433             )
2434             unpack_cmd = 'unzip %s %s' % (ua_option, archive)
2435 
2436         try:
2437             run_cmd_dropstdout(unpack_cmd, as_root=as_root)
2438         except RunCommandError as e:
2439             raise InstallArchiveError("Unable to unpack FSL.")
2440 
2441         new_fsl = os.path.join(tempfolder, 'fsl')
2442         if os.path.exists(fsldir):
2443             # move old one out of way
2444             try:
2445                 old_version = get_installed_version(fsldir)
2446             except (NotAFslVersion, GetInstalledVersionError) as e:
2447                 if keep_old:
2448                     old_version = Version('0.0.0')
2449                     MsgUser.warning(
2450                             "The contents of %s doesn't look like an "
2451                             "FSL installation! - "
2452                             "moving to fsl-0.0.0" % (fsldir))
2453             old_fsl = '-'.join((fsldir, str(old_version)))
2454             if os.path.exists(old_fsl):
2455                 MsgUser.debug(
2456                         "Looks like there is another copy of the "
2457                         "old version of FSL - deleting...")
2458                 try:
2459                     safe_delete(old_fsl, as_root)
2460                 except SafeDeleteError as e:
2461                     raise InstallError(
2462                             ";".join((
2463                                     "Install location already has a "
2464                                     "%s - I've tried to delete it but"
2465                                     " failed" % (old_fsl), str(e))))
2466 
2467             if keep_old:
2468                 try:
2469                     MsgUser.debug(
2470                         "Moving %s to %s" % (fsldir, old_fsl))
2471                     move(fsldir, old_fsl, as_root)
2472                     MsgUser.message(
2473                         '''You can find your archived version of FSL in %s.
2474 If you wish to restore it, remove %s and rename %s to %s''' % (
2475                             old_fsl, fsldir, old_fsl, fsldir))
2476 
2477                 except MoveError as mv_e:
2478                     # failed to move the old version
2479                     MsgUser.debug(
2480                         "Failed to move old version "
2481                         "- %s" % (str(mv_e)))
2482                     raise InstallError(
2483                         "Failed to backup old version (%s)" % (str(mv_e)))
2484             else:
2485                 MsgUser.debug("Removing existing FSL install")
2486                 try:
2487                     safe_delete(fsldir, as_root)
2488                     MsgUser.debug("Deleted %s." % (fsldir))
2489                 except SafeDeleteError as e:
2490                     raise InstallError(
2491                             "Failed to delete %s - %s." % (fsldir, str(e)))
2492         else:
2493             old_fsl = ''
2494         try:
2495             MsgUser.debug("Moving %s to %s" % (new_fsl, fsldir))
2496             move(new_fsl, fsldir, as_root)
2497         except MoveError as e:
2498             # Unable to move new install into place
2499             MsgUser.debug(
2500                     "Move failed - %s." % (str(e)))
2501             raise InstallError(
2502                     'Failed to move new version into place.')
2503 
2504     except InstallError as e:
2505         clean_up_temp()
2506         raise InstallArchiveError(str(e))
2507 
2508     clean_up_temp()
2509     MsgUser.debug("Install complete")
2510     MsgUser.ok("FSL software installed.")
2511     return fsldir
2512 
2513 
2514 def check_for_updates(url, fsldir, requested_v=None):
2515     # Start an update
2516     MsgUser.message("Looking for new version.")
2517     try:
2518         this_version = get_installed_version(fsldir)
2519     except GetInstalledVersionError as e:
2520         # We can't find an installed version of FSL!
2521         raise InstallError(str(e))
2522     else:
2523         MsgUser.debug("You have version %s" % (this_version))
2524         if not requested_v:
2525             version = Version(latest_release(url)['version'])
2526         else:
2527             try:
2528                 version = Version(requested_v)
2529             except NotAFslVersion:
2530                 raise InstallError(
2531                         "%s doesn't look like a version" % requested_v)
2532 
2533         if version > this_version:
2534             # Update Available
2535             if version.major > this_version.major:
2536                 # We don't support patching between major
2537                 # versions so download a fresh copy
2538                 return (UPGRADE, version)
2539             else:
2540                 return (UPDATE, version)
2541         else:
2542             return (CURRENT, None)
2543 
2544 
2545 class MakeApplicationLinksError(Exception):
2546     def __init__(self, *args):
2547         super(MakeApplicationLinksError, self).__init__(*args)
2548         try:
2549             self.app_messages = args[0]
2550         except IndexError:
2551             self.app_messages = []
2552 
2553 
2554 def make_applications_links(fsldir, apps):
2555     '''Create symlinks in /Applications'''
2556     MsgUser.message("Creating Application links...")
2557     results = {}
2558     for app in apps:
2559         app_location = os.path.join('/Applications', os.path.basename(app))
2560         app_target = os.path.join(fsldir, app)
2561         create_link = True
2562         MsgUser.debug("Looking for existing link %s" % (app_location))
2563         if os.path.lexists(app_location):
2564             MsgUser.debug(
2565                     "Is a link: %s; realpath: %s" % (
2566                             os.path.islink(app_location),
2567                             os.path.realpath(app_location)))
2568             if os.path.islink(app_location):
2569                 MsgUser.debug("A link already exists.")
2570                 if os.path.realpath(app_location) != app_target:
2571                     MsgUser.debug(
2572                         "Deleting old (incorrect) link %s" % (app_location))
2573                     try:
2574                         run_cmd_dropstdout("rm " + app_location, as_root=True)
2575                     except RunCommandError as e:
2576                         MsgUser.debug(
2577                                 "Unable to remove broken"
2578                                 " link to %s (%s)." % (app_target, str(e)))
2579                         results[app] = 'Unable to remove broken link to %s' % (
2580                             app_target)
2581                         create_link = False
2582                 else:
2583                     MsgUser.debug("Link is correct, skipping.")
2584                     create_link = False
2585             else:
2586                 MsgUser.debug(
2587                         "%s doesn't look like a symlink, "
2588                         "so let's not delete it." % (app_location))
2589                 results[app] = (
2590                     "%s is not a link so hasn't been updated to point at the "
2591                     "new FSL install.") % (app_location)
2592                 create_link = False
2593         if create_link:
2594             MsgUser.debug('Create a link for %s' % (app))
2595             if os.path.exists(app_target):
2596                 try:
2597                     run_cmd_dropstdout(
2598                             "ln -s %s %s" % (app_target, app_location),
2599                             as_root=True)
2600                 except RunCommandError as e:
2601                     MsgUser.debug(
2602                             "Unable to create link to %s (%s)." % (
2603                                     app_target, str(e)))
2604                     results[app] = (
2605                         'Unable to create link to %s.') % (app_target)
2606             else:
2607                 MsgUser.debug(
2608                     'Unable to find application'
2609                     ' %s to link to.') % (app_target)
2610     if results:
2611         raise MakeApplicationLinksError(results)
2612 
2613 
2614 class CheckX11Warning(Exception):
2615     pass
2616 
2617 
2618 def check_X11(x11):
2619     '''Function to find X11 install on Mac OS X and confirm it is compatible.
2620      Advise user to download Xquartz if necessary'''
2621 
2622     MsgUser.message(
2623         "Checking for X11 windowing system (required for FSL GUIs).")
2624 
2625     xbin = ''
2626 
2627     for x in x11['apps']:
2628         if os.path.exists(os.path.join(x11['location'], x)):
2629             xbin = x
2630 
2631     if xbin != '':
2632         # Find out what version is installed
2633         x_v_cmd = [
2634                 '/usr/bin/mdls', '-name',
2635                 'kMDItemVersion', os.path.join(x11['location'], xbin)]
2636         try:
2637             cmd = Popen(x_v_cmd, stdout=PIPE, stderr=STDOUT, universal_newlines=True)
2638             (vstring, _) = cmd.communicate()
2639         except Exception as  e:
2640             raise CheckX11Warning(
2641                 "Unable to check X11 version (%s)" % (str(e)))
2642         if cmd.returncode:
2643             MsgUser.debug("Error finding the version of X11 (%s)" % (vstring))
2644             # App found, but can't tell version, warn the user
2645             raise CheckX11Warning(
2646                     "X11 (required for FSL GUIs) is installed but I"
2647                     " can't tell what the version is.")
2648         else:
2649             # Returns:
2650             # kMDItemVersion = "2.3.6"\n
2651             (_, _, version) = vstring.strip().split()
2652             if version.startswith('"'):
2653                 version = version[1:-1]
2654             if version in x11['bad_versions']:
2655                 raise CheckX11Warning(
2656                         "X11 (required for FSL GUIs) is a version that"
2657                         " is known to cause problems. We suggest you"
2658                         " upgrade to the latest XQuartz release from "
2659                         "%s" % (x11['download_url']))
2660             else:
2661                 MsgUser.debug(
2662                         "X11 found and is not a bad version"
2663                         " (%s: %s)." % (xbin, version))
2664     else:
2665         # No X11 found, warn the user
2666         raise CheckX11Warning(
2667                 "The FSL GUIs require the X11 window system which I can't"
2668                 " find in the usual places. You can download a copy from %s"
2669                 " - you will need to install this before the GUIs will"
2670                 " function" % (x11['download_url']))
2671 
2672 
2673 def do_install(options, settings):
2674     MsgUser.message(
2675         shell_colours.bold + settings.title + shell_colours.default)
2676 
2677     if options.test_installer:
2678         settings.main_mirror = options.test_installer
2679 
2680     this_computer = Host
2681     if not this_computer.supported:
2682         MsgUser.debug("Unsupported host %s %s %s" % (
2683                         this_computer.o_s,
2684                         this_computer.arch,
2685                         this_computer.os_type))
2686         raise InstallError(
2687             "Unsupported host - you could try building from source")
2688 
2689     if this_computer.o_s == "linux":
2690         system_environment = True
2691         with_matlab = False
2692         application_links = False
2693         x11 = False
2694     elif this_computer.o_s == "darwin":
2695         system_environment = False
2696         with_matlab = True
2697         application_links = True
2698         x11 = True
2699     else:
2700         MsgUser.debug("Unrecognised OS %s" % (this_computer.o_s))
2701         raise InstallError("Unrecognised OS")
2702 
2703     my_uid = os.getuid()
2704 
2705     def configure_environment(fsldir, env_all=False, skip=False, matlab=False):
2706         if skip:
2707             return
2708         if env_all:
2709             if system_environment:
2710                 # Setup the system-wise environment
2711                 try:
2712                     setup_system_environment(fsldir)
2713                 except SetupEnvironmentError as e:
2714                     MsgUser.debug(str(e))
2715                     MsgUser.failed(
2716                         "Failed to configure system-wide profiles "
2717                         "with FSL settings: %s" % (str(e)))
2718                 except SetupEnvironmentSkip as e:
2719                     MsgUser.skipped(
2720                         "Some shells already configured: %s" % (str(e)))
2721                 else:
2722                     MsgUser.debug("System-wide profiles setup.")
2723                     MsgUser.ok("System-wide FSL configuration complete.")
2724             else:
2725                 MsgUser.skipped(
2726                     "System-wide profiles not supported on this OS")
2727         elif my_uid != 0:
2728             # Setup the environment for the current user
2729             try:
2730                 setup_environment(fsldir, with_matlab=matlab)
2731             except SetupEnvironmentError as e:
2732                 MsgUser.debug(str(e))
2733                 MsgUser.failed(str(e))
2734             else:
2735                 MsgUser.ok(
2736                     "User profile updated with FSL settings, you will need "
2737                     "to log out and back in to use the FSL tools.")
2738 
2739     if my_uid != 0:
2740         if options.quiet:
2741             settings.inst_qus.defaults = True
2742             print('''
2743 We may need administrator rights, but you have specified fully automated
2744 mode - you may still be asked for an admin password if required.''')
2745             print('''
2746 To install fully automatedly, either ensure this is running as the root
2747 user (use sudo) or that you can write to the folder you wish to install
2748 FSL in.''')
2749         elif (not options.download and
2750                 not options.list_versions and
2751                 not options.list_builds and
2752                 not options.get_source and
2753                 not options.get_feeds):
2754             MsgUser.warning(
2755                 '''Some operations of the installer require administative rights,
2756     for example installing into the default folder of /usr/local.
2757     If your account is an 'Administrator' (you have 'sudo' rights)
2758     then you will be prompted for your administrator password
2759     when necessary.''')
2760     if not options.d_dir and options.quiet:
2761         raise InstallError(
2762             "Quiet mode requires you to specify the install location"
2763             " (e.g. /usr/local)")
2764     if not options.quiet and not (options.list_versions or options.list_builds):
2765         MsgUser.message(
2766             "When asked a question, the default answer is given in square "
2767             "brackets.\nHit the Enter key to accept this default answer.")
2768     if options.env_only and my_uid != 0:
2769         configure_environment(
2770             get_fsldir(specified_dir=options.d_dir),
2771             options.env_all)
2772         return
2773     if options.archive:
2774         if not options.skipchecksum:
2775             if not options.checksum:
2776                 raise InstallError(
2777                     "No checksum provided and checking not disabled")
2778             else:
2779                 checksummer = globals()[options.checksum_type + 'File']
2780                 if options.checksum != checksummer(options.archive):
2781                     raise InstallError("FSL archive doesn't match checksum")
2782                 else:
2783                     MsgUser.ok("FSL Package looks good")
2784         arc_version = archive_version(options.archive)
2785         MsgUser.message(
2786             "Installing FSL software version %s..." % (arc_version))
2787 
2788         fsldir = install_archive(
2789             archive=options.archive, fsldir=options.d_dir)
2790         try:
2791             post_install(fsldir=fsldir, settings=settings, quiet=options.quiet)
2792         except PostInstallError as e:
2793             raise InstallError(str(e))
2794         configure_environment(
2795             fsldir=fsldir, env_all=options.env_all,
2796             skip=options.skip_env, matlab=with_matlab)
2797         return
2798 
2799     # All the following options require the Internet...
2800     try:
2801         settings.mirror = fastest_mirror(
2802             settings.mirrors, settings.mirrors_file)
2803     except SiteNotResponding as e:
2804         # We can't find the FSL site - possibly the internet is down
2805         raise InstallError(e)
2806 
2807     try:
2808         self_update(settings.mirror)
2809     except SelfUpdateError as e:
2810         MsgUser.debug("Self update error: %s" % (str(e)))
2811         MsgUser.warning("Error checking for updates to installer - continuing")
2812     if options.list_versions:
2813         # Download a list of available downloads from the webserver
2814         list_releases(settings.mirror)
2815         return
2816     if options.list_builds:
2817         # List all available builds
2818         list_builds(settings.mirror)
2819         return
2820 
2821     if options.download:
2822         MsgUser.debug("Attempting to download latest release")
2823         try:
2824             download_release(settings.mirror, request_version=options.requestversion,
2825                              skip_verify=options.skipchecksum)
2826         except DownloadFileError as e:
2827             raise "Unable to download release %s"
2828         return
2829 
2830     if options.update:
2831         fsldir = get_fsldir()
2832         status, new_v = check_for_updates(settings.mirror, fsldir=fsldir)
2833         if status == UPDATE:
2834             MsgUser.ok("Version %s available." % new_v)
2835             if not settings.inst_qus.ask_question('update'):
2836                 return
2837         elif status == UPGRADE:
2838             MsgUser.ok("Version %s available." % new_v)
2839             if not settings.inst_qus.ask_question('upgrade'):
2840                 return
2841         else:
2842             MsgUser.ok("FSL is up-to-date.")
2843             return
2844 
2845     if options.get_source:
2846         MsgUser.debug("Attempting to download source")
2847         try:
2848             download_release(
2849                 settings.mirror,
2850                 request_version=options.requestversion,
2851                 skip_verify=options.skipchecksum,
2852                 source_code=True)
2853         except DownloadFileError as e:
2854             raise "Unable to download source code %s"
2855         return
2856 
2857     if options.get_feeds:
2858         MsgUser.debug("Attempting to download FEEDS")
2859         try:
2860             download_release(
2861                 settings.mirror,
2862                 request_version=options.requestversion,
2863                 skip_verify=options.skipchecksum,
2864                 feeds=True)
2865         except DownloadFileError as e:
2866             raise "Unable to download FEEDS %s"
2867         return
2868 
2869     try:
2870         (version, details) = get_web_version_and_details(
2871             Settings.mirror,
2872             request_version=options.requestversion)
2873         if 'redirect' in details:
2874             MsgUser.message("Please download FSL using the instructions here:")
2875             MsgUser.message("%s" % (details['redirect']))
2876             return
2877 
2878         fsldir = get_fsldir(specified_dir=options.d_dir, install=True)
2879         reinstall = True
2880         if os.path.exists(fsldir):
2881             inst_version = get_installed_version(fsldir)
2882             if inst_version == version:
2883                 reinstall = Settings.inst_qus.ask_question('version_match')
2884         if reinstall:
2885             (fname, version, details) = download_release(
2886                 Settings.mirror,
2887                 to_temp=True,
2888                 request_version=options.requestversion,
2889                 skip_verify=options.skipchecksum)
2890             if not details['supported']:
2891                 MsgUser.debug(
2892                     "This OS is not officially supported -"
2893                     " you may experience issues"
2894                 )
2895             MsgUser.debug(
2896                 "Installing %s from %s (details: %s)" % (
2897                     fname, version, details))
2898             MsgUser.message(
2899                 "Installing FSL software version %s..." % (version))
2900             install_archive(
2901                 archive=fname, fsldir=fsldir)
2902             try:
2903                 safe_delete(fname)
2904             except SafeDeleteError as e:
2905                 MsgUser.debug(
2906                     "Unable to delete downloaded package %s ; %s" % (
2907                         fname, str(e)))
2908             if details['notes']:
2909                 MsgUser.message(details['notes'])
2910             try:
2911                 post_install(
2912                     fsldir=fsldir, settings=settings,
2913                     quiet=options.quiet, x11=x11,
2914                     app_links=application_links)
2915             except PostInstallError as e:
2916                 raise InstallError(str(e))
2917 
2918     except DownloadError as e:
2919         MsgUser.debug("Unable to download FSL %s" % (str(e)))
2920         raise InstallError("Unable to download FSL")
2921     except InstallArchiveError as e:
2922         MsgUser.debug("Unable to unpack FSL ; %s" % (str(e)))
2923         raise InstallError("Unable to unpack FSL - %s" % (str(e)))
2924 
2925     configure_environment(
2926         fsldir=fsldir, env_all=options.env_all,
2927         skip=options.skip_env, matlab=with_matlab)
2928 
2929     if details['notes']:
2930         MsgUser.message(details['notes'])
2931 
2932 
2933 def parse_options(args):
2934     usage = "usage: %prog [options]"
2935     ver = "%%prog %s" % (version)
2936     parser = OptionParser(usage=usage, version=ver)
2937     parser.add_option("-d", "--dest", dest="d_dir",
2938                       help="Install into folder given by DESTDIR - "
2939                       "e.g. /usr/local/fsl",
2940                       metavar="DESTDIR", action="store",
2941                       type="string")
2942     parser.add_option("-e", dest="env_only",
2943                       help="Only setup/update your environment",
2944                       action="store_true")
2945     parser.add_option("-E", dest="env_all",
2946                       help="Setup/update the environment for ALL users",
2947                       action="store_true")
2948     parser.add_option("-v", help="Print version number and exit",
2949                       action="version")
2950     parser.add_option("-c", "--checkupdate", dest='update',
2951                       help="Check for FSL updates -"
2952                       " needs an internet connection",
2953                       action="store_true")
2954     parser.add_option("-o", "--downloadonly", dest="download",
2955                       help=SUPPRESS_HELP,
2956                       action="store_true")
2957 
2958     advanced_group = OptionGroup(
2959             parser, "Advanced Install Options",
2960             "These are advanced install options")
2961     advanced_group.add_option(
2962             "-l", "--listversions", dest="list_versions",
2963             help="List available versions of FSL",
2964             action="store_true")
2965     advanced_group.add_option(
2966             "-b", "--listbuilds", dest="list_builds",
2967             help="List available FSL builds",
2968             action="store_true")
2969     advanced_group.add_option(
2970             "-B", "--fslbuild", dest="requestbuild",
2971             help="Download the specific FSLBUILD of FSL",
2972             metavar="FSLBUILD", action="store",
2973             type="string")
2974     advanced_group.add_option(
2975             "-V", "--fslversion", dest="requestversion",
2976             help="Download the specific version FSLVERSION of FSL",
2977             metavar="FSLVERSION", action="store",
2978             type="string")
2979     advanced_group.add_option(
2980             "-s", "--source", dest="get_source",
2981             help="Download source code for FSL",
2982             action="store_true")
2983     advanced_group.add_option(
2984             "-F", "--feeds", dest="get_feeds",
2985             help="Download FEEDS",
2986             action="store_true")
2987     advanced_group.add_option(
2988             "-q", "--quiet", dest='quiet',
2989             help="Silence all messages - useful if scripting install",
2990             action="store_true")
2991     advanced_group.add_option(
2992             "-p", dest="skip_env",
2993             help="Don't setup the environment",
2994             action="store_true")
2995     parser.add_option_group(advanced_group)
2996 
2997     debug_group = OptionGroup(
2998         parser, "Debugging Options",
2999         "These are for use if you have a problem running this installer.")
3000     debug_group.add_option(
3001         "-f", "--file", dest="archive",
3002         help="Install a pre-downloaded copy of the FSL archive",
3003         metavar="ARCHIVEFILE", action="store",
3004         type="string")
3005     debug_group.add_option(
3006         "-C", "--checksum", dest="checksum",
3007         help="Supply the expected checksum for the pre-downloaded FSL archive",
3008         metavar="CHECKSUM", action="store",
3009         type="string")
3010     debug_group.add_option(
3011         "-T", "--checksum-type", dest="checksum_type",
3012         default="sha256",
3013         help="Specify the type of checksum",
3014         action="store",
3015         type="string")
3016     debug_group.add_option(
3017         "-M", "--nochecksum", dest="skipchecksum",
3018         help="Don't check the pre-downloaded FSL archive",
3019         action="store_true")
3020     debug_group.add_option(
3021         "-D", dest="verbose",
3022         help="Switch on debug messages",
3023         action="store_true")
3024     debug_group.add_option(
3025         "-G", dest="test_installer",
3026         help=SUPPRESS_HELP,
3027         action="store",
3028         type="string")
3029     parser.add_option_group(debug_group)
3030     return parser.parse_args(args)
3031 
3032 
3033 def override_host(requestbuild):
3034     '''Overrides attributes of the Host class in the event that the user
3035     has requested a specific FSL build.
3036     '''
3037     if requestbuild == 'centos7_64':
3038         Host.o_s       = 'linux'
3039         Host.arch      = 'x86_64'
3040         Host.vendor    = 'centos'
3041         Host.version   = Version('7.8.2003')
3042         Host.glibc     = '2.2.5'
3043         Host.supported = True
3044         Host.bits      = '64'
3045     elif requestbuild == 'centos6_64':
3046         Host.o_s       = 'linux'
3047         Host.arch      = 'x86_64'
3048         Host.vendor    = 'centos'
3049         Host.version   = Version('6.10')
3050         Host.glibc     = '2.2.5'
3051         Host.supported = True
3052         Host.bits      = '64'
3053     elif requestbuild == 'macOS_64':
3054         Host.o_s       = 'darwin'
3055         Host.arch      = 'x86_64'
3056         Host.vendor    = 'apple'
3057         Host.version   = Version('19.6.0')
3058         Host.glibc     = ''
3059         Host.supported = True
3060         Host.bits      = '64'
3061     # Download x86 version if running on Apple
3062     # M1, as it runs just fine under emulation
3063     elif (requestbuild is None and
3064           Host.o_s == 'darwin' and
3065           Host.arch == 'arm64'):
3066         Host.arch = 'x86_64'
3067 
3068 
3069 def main(argv=None):
3070     if argv is None:
3071         argv = sys.argv[1:]
3072     (options, args) = parse_options(argv)
3073     if options.verbose:
3074         MsgUser.debugOn()
3075         print(options)
3076     if options.quiet:
3077         MsgUser.quietOn()
3078     override_host(options.requestbuild)
3079 
3080     installer_settings = Settings()
3081     try:
3082         do_install(options, installer_settings)
3083     except BadVersion as e:
3084         MsgUser.debug(str(e))
3085         MsgUser.failed("Unable to find requested version!")
3086         sys.exit(1)
3087     except (InstallError, GetFslDirError, GetInstalledVersionError) as e:
3088         MsgUser.failed(str(e))
3089         sys.exit(1)
3090     except UnsupportedOs as e:
3091         MsgUser.failed(str(e))
3092         sys.exit(1)
3093     except KeyboardInterrupt as e:
3094         MsgUser.message('')
3095         MsgUser.failed("Install aborted.")
3096         sys.exit(1)
3097 
3098 
3099 if __name__ == '__main__':
3100     main()

Attached Files

To refer to attachments on a page, use attachment:filename, as shown below in the list of files. Do NOT use the URL of the [get] link, since this is subject to change and can break easily.
  • [get | view] (10:10:06 22-11-2022, 101.4 KB) [[attachment:fslinstaller.py]]
 All files | Selected Files: delete move to page copy to page

You are not allowed to attach a file to this page.