Run the task
(self, fp)
| 129 | self.__dict__.update(kwargs) |
| 130 | |
| 131 | def execute(self, fp): |
| 132 | """Run the task""" |
| 133 | import subprocess |
| 134 | |
| 135 | use_shell = not isinstance(self.command, list) |
| 136 | if "literal" in self.__dict__: |
| 137 | fp.write(self.literal.encode("utf-8")) |
| 138 | return 0 |
| 139 | |
| 140 | env = None |
| 141 | if "addenv" in self.__dict__: |
| 142 | env = os.environ.copy() |
| 143 | env.update(self.addenv) |
| 144 | try: |
| 145 | p = subprocess.Popen( |
| 146 | self.command, |
| 147 | bufsize=-1, |
| 148 | stdin=subprocess.PIPE, |
| 149 | stdout=subprocess.PIPE, |
| 150 | stderr=subprocess.STDOUT, |
| 151 | shell=use_shell, |
| 152 | env=env, |
| 153 | ) |
| 154 | except OSError as e: |
| 155 | # if use_shell is False then Popen may raise exception |
| 156 | # if binary is missing. In this case we mimic what |
| 157 | # shell does. Namely, complaining to stderr and |
| 158 | # setting non-zero status code. It's might also |
| 159 | # automatically handle things like "failed to fork due |
| 160 | # to some system limit". |
| 161 | fp.write(f"Failed to execute {self.command}: {e}".encode("utf-8")) |
| 162 | return 127 |
| 163 | p.stdin.close() |
| 164 | |
| 165 | timer = None |
| 166 | timer_fired = threading.Event() |
| 167 | |
| 168 | if self.timeout is not None and hasattr(p, "kill"): |
| 169 | |
| 170 | def on_timeout(): |
| 171 | p.kill() |
| 172 | timer_fired.set() |
| 173 | |
| 174 | timer = threading.Timer(self.timeout, on_timeout) |
| 175 | timer.start() |
| 176 | |
| 177 | try: |
| 178 | while True: |
| 179 | data = p.stdout.read(64 * 1024) |
| 180 | if not data: |
| 181 | break |
| 182 | |
| 183 | fp.write(data) |
| 184 | finally: |
| 185 | if timer is not None: |
| 186 | timer.cancel() |
| 187 | timer.join() |
| 188 |