~roadmr/ubuntu/precise/checkbox/0.13.1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
#
# This file is part of Checkbox.
#
# Copyright 2008 Canonical Ltd.
#
# Checkbox is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Checkbox is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Checkbox.  If not, see <http://www.gnu.org/licenses/>.
#
import os
import time
import select


class Process:
    """Class representing a child process which is non blocking. This makes
       it possible to return within a specified timeout."""

    def __init__(self, cmd, env={}, bufsize=8192):
        """The parameter 'cmd' is the shell command to execute in a
        sub-process. If the 'bufsize' parameter is specified, it
        specifies the size of the I/O buffers from the child process."""
        self.cleaned=False
        self.BUFSIZ=bufsize
        self.outr, self.outw = os.pipe()
        self.errr, self.errw = os.pipe()
        self.pid = os.fork()
        if self.pid == 0:
            self._child(cmd, env)
        os.close(self.outw) #parent doesn't write so close
        os.close(self.errw)
        # Note we could use self.stdout=fdopen(self.outr) here
        # to get a higher level file object like popen2.Popen3 uses.
        # This would have the advantages of auto handling the BUFSIZ
        # and closing the files when deleted. However it would mean
        # that it would block waiting for a full BUFSIZ unless we explicitly
        # set the files non blocking, and there would be extra uneeded
        # overhead like EOL conversion. So I think it's handier to use os.read()
        self.outdata = self.errdata = ""
        self.starttime = self.endtime = None
        self._outeof = self._erreof = 0

    def _child(self, cmd, env):
        # Note sh below doesn't setup a seperate group (job control)
        # for non interactive shells (hmm maybe -m option does?)
        os.setpgrp() #seperate group so we can kill it
        os.dup2(self.outw,1) #stdout to write side of pipe
        os.dup2(self.errw,2) #stderr to write side of pipe
        #stdout & stderr connected to pipe, so close all other files
        map(os.close, [self.outr,self.outw,self.errr,self.errw])
        try:
            cmd = ["/bin/sh", "-c", cmd]
            os.execve(cmd[0], cmd, env)
        finally: #exit child on error
            os._exit(1)

    def read(self, timeout=None):
        """return 0 when finished
           else return 1 every timeout seconds
           data will be in outdata and errdata"""
        self.starttime = time.time()
        while 1:
            tocheck=[]
            if not self._outeof:
                tocheck.append(self.outr)
            if not self._erreof:
                tocheck.append(self.errr)
            ready = select.select(tocheck, [], [], timeout)
            if len(ready[0]) == 0: #no data timeout
                return 1
            else:
                if self.outr in ready[0]:
                    outchunk = os.read(self.outr, self.BUFSIZ)
                    if outchunk == "":
                        self._outeof = 1
                    self.outdata += outchunk
                if self.errr in ready[0]:
                    errchunk = os.read(self.errr, self.BUFSIZ)
                    if errchunk == "":
                        self._erreof = 1
                    self.errdata += errchunk
                if self._outeof and self._erreof:
                    self.endtime = time.time()
                    return 0
                elif timeout:
                    if (time.time() - self.starttime) > timeout:
                        return 1 # may be more data but time to go

    def kill(self):
        os.kill(-self.pid, 15) # SIGTERM whole group

    def cleanup(self):
        """Wait for and return the exit status of the child process."""
        self.cleaned = True
        os.close(self.outr)
        os.close(self.errr)
        pid, status = os.waitpid(self.pid, 0)
        if pid == self.pid:
            self.status = status
        return self.status

    def __del__(self):
        if not self.cleaned:
            self.cleanup()