| #!/usr/bin/python |
| """Utility functions for scripts in my bin diretory. |
| |
| This module contains common utilities such as wrappers for |
| error/warning reporting, executing shell commands in a controlled way, |
| etc. These functions are shared by a number of helper scripts. |
| |
| """ |
| |
| import locale |
| import os |
| import re |
| import shlex |
| import signal |
| import subprocess |
| import sys |
| import tempfile |
| |
| # Debugging verbosity level (0 -> no output) |
| flag_debug = 0 |
| |
| # Unit testing mode. If set to 1, throw exception instead of calling exit() |
| flag_unittest = 0 |
| |
| hrszre = re.compile(r"^([\d\.]+)(\S)$") |
| factors = {"K": 1024.0, "M": 1048576.0, "G": 1073741824.0} |
| |
| |
| def verbose(level, msg): |
| """Print debug trace output of verbosity level is >= value in 'level'.""" |
| if level <= flag_debug: |
| sys.stderr.write(msg + "\n") |
| |
| |
| def verbosity_level(): |
| """Return debug trace level.""" |
| return flag_debug |
| |
| |
| def increment_verbosity(): |
| """Increment debug trace level by 1.""" |
| global flag_debug |
| flag_debug += 1 |
| |
| |
| def decrement_verbosity(): |
| """Lower debug trace level by 1.""" |
| global flag_debug |
| flag_debug -= 1 |
| |
| |
| def unit_test_enable(): |
| """Set unit testing mode.""" |
| global flag_unittest |
| sys.stderr.write("+++ unit testing mode enabled +++\n") |
| flag_unittest = 1 |
| |
| |
| def warning(msg): |
| """Issue a warning to stderr.""" |
| sys.stderr.write("warning: " + msg + "\n") |
| |
| |
| def error(msg): |
| """Issue an error to stderr, then exit.""" |
| errm = "error: " + msg + "\n" |
| sys.stderr.write(errm) |
| if flag_unittest: |
| raise Exception(errm) |
| else: |
| exit(1) |
| |
| |
| def docmd(cmd): |
| """Run a command via subprocess, issuing fatal error if cmd fails.""" |
| args = shlex.split(cmd) |
| verbose(2, "+ docmd executing: %s" % cmd) |
| rc = subprocess.call(args) |
| if rc != 0: |
| error("command failed: %s" % cmd) |
| |
| |
| # Similar to docmd, but return status after issuing failure message |
| def docmdnf(cmd): |
| """Run a command via subprocess, returning exit status.""" |
| args = shlex.split(cmd) |
| verbose(2, "+ docmd executing: %s" % cmd) |
| rc = subprocess.call(args) |
| return rc |
| |
| |
| # Similar to docmd, but suppress output |
| def doscmd(cmd, nf=None): |
| """Run a command via subprocess, suppressing output unless error.""" |
| verbose(2, "+ doscmd executing: %s" % cmd) |
| args = shlex.split(cmd) |
| cmdtf = tempfile.NamedTemporaryFile(mode="w", delete=True) |
| rc = subprocess.call(args, stdout=cmdtf, stderr=cmdtf) |
| if rc != 0: |
| warning("error: command failed (rc=%d) cmd: %s" % (rc, cmd)) |
| warning("output from failing command:") |
| subprocess.call(["cat", cmdtf.name]) |
| if nf: |
| return None |
| error("") |
| cmdtf.close() |
| return True |
| |
| |
| # invoke command, writing output to file |
| def docmdout(cmd, outfile, nf=None): |
| """Run a command via subprocess, writing output to a file.""" |
| verbose(2, "+ docmdout executing: %s > %s" % (cmd, outfile)) |
| args = shlex.split(cmd) |
| with open(outfile, "w") as outfile: |
| rc = subprocess.call(args, stdout=outfile) |
| if rc != 0: |
| warning("error: command failed (rc=%d) cmd: %s" % (rc, cmd)) |
| if nf: |
| return None |
| error("") |
| return True |
| |
| # invoke command, writing output to file |
| def docmderrout(cmd, outfile, nf=None): |
| """Run a command via subprocess, writing output to a file.""" |
| verbose(2, "+ docmdout executing: %s > %s" % (cmd, outfile)) |
| args = shlex.split(cmd) |
| try: |
| with open(outfile, "w") as outfile: |
| rc = subprocess.call(args, stdout=outfile, stderr=outfile) |
| if rc != 0: |
| if nf: |
| sys.stderr.write("error: command failed (rc=%d) cmd: %s\n" % (rc, cmd)) |
| return rc |
| else: |
| error("command failed (rc=%d) cmd: %s\n" % (rc, cmd)) |
| return rc |
| except IOError: |
| error("unable to open %s for writing" % outfile) |
| |
| |
| # invoke command, reading from one file and writing to another |
| def docmdinout(cmd, infile, outfile): |
| """Run a command via subprocess with input and output file.""" |
| verbose(2, "+ docmdinout executing: %s < %s > %s" % (cmd, infile, outfile)) |
| args = shlex.split(cmd) |
| cmdtf = tempfile.NamedTemporaryFile(mode="w", delete=True) |
| with open(infile, "r") as inf: |
| with open(outfile, "w") as outf: |
| rc = subprocess.call(args, stdout=outf, stdin=inf, stderr=cmdtf) |
| if rc != 0: |
| warning("error: command failed (rc=%d) cmd: %s" % (rc, cmd)) |
| warning("output from failing command:") |
| subprocess.call(["cat", cmdtf.name]) |
| return 1 |
| verbose(2, "+ finished: %s < %s > %s" % (cmd, infile, outfile)) |
| return 0 |
| |
| |
| # invoke command, returning array of lines read from it |
| def docmdlines(cmd, nf=None): |
| """Run a command via subprocess, returning output as an array of lines.""" |
| verbose(2, "+ docmdlines executing: %s" % cmd) |
| args = shlex.split(cmd) |
| mypipe = subprocess.Popen(args, stdout=subprocess.PIPE) |
| encoding = locale.getdefaultlocale()[1] |
| if encoding is None: |
| encoding = 'UTF-8' |
| pout, perr = mypipe.communicate() |
| if mypipe.returncode != 0: |
| if perr: |
| decoded_err = perr.decode(encoding) |
| warning(decoded_err) |
| if nf: |
| return None |
| error("command failed (rc=%d): cmd was %s" % (mypipe.returncode, args)) |
| decoded = pout.decode(encoding) |
| lines = decoded.strip().split("\n") |
| return lines |
| |
| |
| # invoke command, returning raw bytes from read |
| def docmdbytes(cmd, nf=None): |
| """Run a command via subprocess, returning output as raw bytestring.""" |
| args = shlex.split(cmd) |
| mypipe = subprocess.Popen(args, stdout=subprocess.PIPE) |
| pout, perr = mypipe.communicate() |
| if mypipe.returncode != 0: |
| encoding = locale.getdefaultlocale()[1] |
| if perr: |
| decoded_err = perr.decode(encoding) |
| warning(decoded_err) |
| if nf: |
| return None |
| error("command failed (rc=%d): cmd was %s" % (mypipe.returncode, args)) |
| return pout |
| |
| |
| # invoke a command with input coming from an echo'd string, e.g. |
| # Ex: "echo 1+2 | perl" |
| def docmdinstring(cmd, instring): |
| """Invoke a command with stdin coming from a specific string.""" |
| verbose(2, "+ docmdinstring executing: echo %s | %s " % (cmd, instring)) |
| args = shlex.split(cmd) |
| mypipe = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE) |
| encoding = locale.getdefaultlocale()[1] |
| pout, perr = mypipe.communicate(instring) |
| if mypipe.returncode != 0: |
| if perr: |
| decoded_err = perr.decode(encoding) |
| warning(decoded_err) |
| error("command failed (rc=%d): cmd was %s" % (mypipe.returncode, args)) |
| decoded = pout.decode(encoding) |
| lines = decoded.strip().split("\n") |
| return lines |
| |
| |
| # Execute a command with an alarm timeout. |
| def docmdwithtimeout(cmd, timeout_duration): |
| """Run a command via subprocess, returning exit status or -1 if timeout.""" |
| |
| class TimeoutError(Exception): |
| pass |
| |
| def handler(signum, frame): |
| raise TimeoutError() |
| |
| # set the timeout handler |
| prevhandler = signal.signal(signal.SIGALRM, handler) |
| signal.alarm(timeout_duration) |
| try: |
| result = docmdnf(cmd) |
| except TimeoutError as exc: |
| verbose(1, "timeout triggered after %d seconds" % timeout_duration) |
| result = -1 |
| finally: |
| signal.alarm(0) |
| signal.signal(signal.SIGALRM, prevhandler) |
| return result |
| |
| |
| # perform default locale setup if needed |
| def setdeflanglocale(): |
| if "LANG" not in os.environ: |
| warning("no env setting for LANG -- using default values") |
| os.environ["LANG"] = "en_US.UTF-8" |
| os.environ["LANGUAGE"] = "en_US:" |
| |
| |
| def determine_btrfs_ssdroot(here): |
| """Determine ssd root.""" |
| |
| path_components = here.split("/") |
| root = "/%s" % path_components[1] |
| verbose(2, "cwd=%s root=%s" % (here, root)) |
| |
| # Is this a BTRFS ssd to begin with? |
| outlines = docmdlines("stat -f --printf=%%T %s" % root) |
| if not outlines: |
| error("internal error-- could not determine FS type " |
| "for root dir %s" % root) |
| if outlines[0] != "btrfs": |
| error("current FS type is %s, not btrfs (can't proceed)" % outlines[0]) |
| return root |
| |
| |
| def hr_size_to_bytes(sz): |
| """Convert human readable size back to bytes.""" |
| m = hrszre.match(sz) |
| if not m: |
| warning("unmatchable size expr %s" % sz) |
| return None |
| val = float(m.group(1)) |
| facs = m.group(2) |
| if facs not in factors: |
| warning("unknown factor '%s' in size expr %s" % (facs, sz)) |
| return None |
| fac = factors[facs] |
| nb = int(val * fac) |
| return nb |
| |
| |
| def trim_perf_report_file(infile): |
| """Trim trailing spaces from lines in perf report.""" |
| verbose(1, "trim: reading " + infile) |
| try: |
| f = open(infile, "r") |
| except IOError: |
| warning("unable to open file %s for reading" % infile) |
| return 1 |
| lines = f.readlines() |
| f.close() |
| verbose(1, "trim: rewriting " + infile) |
| try: |
| ft = open(infile, "w") |
| except IOError: |
| warning("unable to open file %s for rewriting" % infile) |
| return 1 |
| for line in lines: |
| sline = line.rstrip() |
| ft.write(sline + "\n") |
| ft.close() |
| return 0 |