Files
munki/code/client/ptyexec
Greg Neagle e1b5d505bc Revert "Revert "munki: rename "/usr/local/munki/python" symlink to "munki-python" (#997)""
IOW, restore the rename of "/usr/local/munki/python" symlink to "munki-python" (#997)

This reverts commit b9f9fffccc.
2020-09-15 16:46:34 -07:00

138 lines
3.4 KiB
Python
Executable File

#!/usr/local/munki/munki-python
# encoding: utf-8
#
# Copyright 2011-2017 Google Inc. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the 'License');
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an 'AS IS' BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
ptyexec
Created by John Randolph 2011-08-11.
Utility script to run a subprocess in a pseudo tty.
This will have the effect of unbuffering output I/O from the subprocess.
stdin of the subprocess is not connected to the stdin of this parent
process.
"""
from __future__ import absolute_import, print_function
import fcntl
import os
import pty
import select
import signal
import sys
# lots of variable names lifted from the C code this has been mostly
# translated from
# pylint: disable=invalid-name
child_exited = {}
def set_file_nonblocking(f, non_blocking=True):
"""Set non-blocking flag on a file object.
Args:
f: file
non_blocking: bool, default True, non-blocking mode or not
"""
flags = fcntl.fcntl(f.fileno(), fcntl.F_GETFL)
if bool(flags & os.O_NONBLOCK) != non_blocking:
flags ^= os.O_NONBLOCK
fcntl.fcntl(f.fileno(), fcntl.F_SETFL, flags)
def sighandler(signum, _frame):
"""Handle a signal.
Args:
signum: int, signal number
frame: frame, stack frame where signal was received
"""
global child_exited
if signum == signal.SIGCHLD:
x = os.waitpid(-1, 0)
if x[0] > 0:
child_exited[x[0]] = x[1] >> 8 # get exit status from LSB
def usage(arg0):
"""Print usage."""
print('Usage: %s [command to run] [arguments...]' % arg0, file=sys.stderr)
return 0
def ptyexec(argv):
"""Setup pty and exec argv.
Args:
argv: list, arguments
Returns:
int, status code from child process upon completion, or 1 if a fork
error occurs.
never returns on the child side of the fork.
"""
signal.signal(signal.SIGCHLD, sighandler)
pid, fd = pty.fork()
if pid == 0: # child
try:
os.execv(argv[0], argv)
except OSError as err:
print(str(err), file=sys.stderr)
sys.exit(1)
elif pid > 0: # parent
f = os.fdopen(fd, 'rb+', 0)
set_file_nonblocking(f)
while True:
try:
(rl, _wl, _xl) = select.select([f], [], [], 5.0)
except select.error:
rl = []
if f in rl:
l = f.read()
try:
sys.stdout.buffer.write(l)
except AttributeError:
# Python 2
sys.stdout.write(l)
sys.stdout.flush()
if pid in child_exited:
break
f.close()
elif pid == -1: # error, never forked.
print('fork() error', file=sys.stderr)
return 1
return child_exited.get(pid, 1)
def main(argv):
"""Main."""
if len(argv) < 2:
return usage(argv[0])
argv.pop(0)
return ptyexec(argv)
if __name__ == '__main__':
sys.exit(main(sys.argv))