~wesley-wiedenmeier/curtin/error-output

« back to all changes in this revision

Viewing changes to curtin/util.py

  • Committer: Scott Moser
  • Date: 2015-12-17 17:03:06 UTC
  • mfrom: (320.2.4 trunk.lp1526127)
  • Revision ID: smoser@ubuntu.com-20151217170306-p3mnmzt3cpxes8us
subp: add decode parameter, defaulting to replace

previously stdout and stderr of a process called by subp would have
decode('utf-8') called on it. If that raised a UnicodeDecodeError
then it would be passed up.

Some programs used by curtin may at times output such binary data.
The example that triggered this was unexpectd binary data in lsblock
output.

Now, the caller can call subp with the decoding that they want.
Default is decode=replace. If decode=False, no decode is done. To get the
old behavior, the user needs to pass 'decode=strict'

Show diffs side-by-side

added added

removed removed

Lines of Context:
34
34
 
35
35
 
36
36
def _subp(args, data=None, rcs=None, env=None, capture=False, shell=False,
37
 
          logstring=False):
 
37
          logstring=False, decode="replace"):
38
38
    if rcs is None:
39
39
        rcs = [0]
40
40
 
57
57
                              stderr=stderr, stdin=stdin,
58
58
                              env=env, shell=shell)
59
59
        (out, err) = sp.communicate(data)
60
 
        if isinstance(out, bytes):
61
 
            out = out.decode('utf-8')
62
 
        if isinstance(err, bytes):
63
 
            err = err.decode('utf-8')
 
60
 
 
61
        # Just ensure blank instead of none.
 
62
        if not out and capture:
 
63
            out = b''
 
64
        if not err and capture:
 
65
            err = b''
 
66
        if decode:
 
67
            def ldecode(data, m='utf-8'):
 
68
                if not isinstance(data, bytes):
 
69
                    return data
 
70
                return data.decode(m, errors=decode)
 
71
 
 
72
            out = ldecode(out)
 
73
            err = ldecode(err)
64
74
    except OSError as e:
65
75
        raise ProcessExecutionError(cmd=args, reason=e)
66
76
    rc = sp.returncode  # pylint: disable=E1101
68
78
        raise ProcessExecutionError(stdout=out, stderr=err,
69
79
                                    exit_code=rc,
70
80
                                    cmd=args)
71
 
    # Just ensure blank instead of none?? (if capturing)
72
 
    if not out and capture:
73
 
        out = ''
74
 
    if not err and capture:
75
 
        err = ''
76
81
    return (out, err)
77
82
 
78
83
 
79
84
def subp(*args, **kwargs):
 
85
    """Run a subprocess.
 
86
 
 
87
    :param args: command to run in a list. [cmd, arg1, arg2...]
 
88
    :param data: input to the command, made available on its stdin.
 
89
    :param rcs:
 
90
        a list of allowed return codes.  If subprocess exits with a value not
 
91
        in this list, a ProcessExecutionError will be raised.  By default,
 
92
        data is returned as a string.  See 'decode' parameter.
 
93
    :param env: a dictionary for the command's environment.
 
94
    :param capture:
 
95
        boolean indicating if output should be captured.  If True, then stderr
 
96
        and stdout will be returned.  If False, they will not be redirected.
 
97
    :param shell: boolean indicating if this should be run with a shell.
 
98
    :param logstring:
 
99
        the command will be logged to DEBUG.  If it contains info that should
 
100
        not be logged, then logstring will be logged instead.
 
101
    :param decode:
 
102
        if False, no decoding will be done and returned stdout and stderr will
 
103
        be bytes.  Other allowed values are 'strict', 'ignore', and 'replace'.
 
104
        These values are passed through to bytes().decode() as the 'errors'
 
105
        parameter.  There is no support for decoding to other than utf-8.
 
106
    :param retries:
 
107
        a list of times to sleep in between retries.  After each failure
 
108
        subp will sleep for N seconds and then try again.  A value of [1, 3]
 
109
        means to run, sleep 1, run, sleep 3, run and then return exit code.
 
110
    """
80
111
    retries = []
81
112
    if "retries" in kwargs:
82
113
        retries = kwargs.pop("retries")