mirror of
https://github.com/CumulusNetworks/ifupdown2.git
synced 2024-05-06 15:54:50 +00:00
Ticket: None Reviewed By: CCR-4692 Testing Done: smoke + scale tests If called with close_fds=True the subprocess module will try to close every fd from 3 to MAXFD before executing the specified command. This is done in Python not even with a C-implementation which truly affecting performances. This patch aims to better handle the file descriptor used by ifupdown2. Either by closing them after use or by setting the close-on-exec flag for the file descriptor, which causes the file descriptor to be automatically (and atomically) closed when any of the exec-family functions succeed. With the actual patch all tests are passing, I can't think of any future issue but if any a possible future modification might be to use the parameter 'preexec_fn', which allows us to set function which will be executed in the child process before executing the command line. We can always manually close any remaining open file descriptors with something like: >>> os.listdir('/proc/self/fd/') ['0', '1', '2', ‘3’, etc..] >>> for fd in os.listdir('/proc/self/fd/') >>> if int(fd) > 2: >>> os.close(fd) This patch is also totally re-organising the use of subprocesses. By removing all subprocess code redundancy.
182 lines
6.2 KiB
Python
182 lines
6.2 KiB
Python
#!/usr/bin/python
|
|
#
|
|
# Copyright 2014 Cumulus Networks, Inc. All rights reserved.
|
|
# Author: Roopa Prabhu, roopa@cumulusnetworks.com
|
|
#
|
|
# utils --
|
|
# helper class
|
|
#
|
|
|
|
import os
|
|
import re
|
|
import shlex
|
|
import fcntl
|
|
import signal
|
|
import logging
|
|
import subprocess
|
|
import ifupdownflags
|
|
|
|
from functools import partial
|
|
|
|
def signal_handler_f(ps, sig, frame):
|
|
if ps:
|
|
ps.send_signal(sig)
|
|
if sig == signal.SIGINT:
|
|
raise KeyboardInterrupt
|
|
|
|
class utils():
|
|
logger = logging.getLogger('ifupdown')
|
|
DEVNULL = open(os.devnull, 'w')
|
|
|
|
@classmethod
|
|
def importName(cls, modulename, name):
|
|
""" Import a named object """
|
|
try:
|
|
module = __import__(modulename, globals(), locals(), [name])
|
|
except ImportError:
|
|
return None
|
|
return getattr(module, name)
|
|
|
|
@classmethod
|
|
def lockFile(cls, lockfile):
|
|
try:
|
|
fp = os.open(lockfile, os.O_CREAT | os.O_TRUNC | os.O_WRONLY)
|
|
fcntl.flock(fp, fcntl.LOCK_EX | fcntl.LOCK_NB)
|
|
fcntl.fcntl(fp, fcntl.F_SETFD, fcntl.FD_CLOEXEC)
|
|
except IOError:
|
|
return False
|
|
return True
|
|
|
|
@classmethod
|
|
def parse_iface_range(cls, name):
|
|
range_match = re.match("^([\w\.]+)\[([\d]+)-([\d]+)\]", name)
|
|
if range_match:
|
|
range_groups = range_match.groups()
|
|
if range_groups[1] and range_groups[2]:
|
|
return (range_groups[0], int(range_groups[1], 10),
|
|
int(range_groups[2], 10))
|
|
return None
|
|
|
|
@classmethod
|
|
def expand_iface_range(cls, name):
|
|
ifacenames = []
|
|
iface_range = cls.parse_iface_range(name)
|
|
if iface_range:
|
|
for i in range(iface_range[1], iface_range[2]):
|
|
ifacenames.append('%s-%d' %(iface_range[0], i))
|
|
return ifacenames
|
|
|
|
@classmethod
|
|
def check_ifname_size_invalid(cls, name=''):
|
|
""" IFNAMSIZ in include/linux/if.h is 16 so we check this """
|
|
IFNAMSIZ = 16
|
|
if len(name) > IFNAMSIZ - 1:
|
|
return True
|
|
else:
|
|
return False
|
|
|
|
@classmethod
|
|
def enable_subprocess_signal_forwarding(cls, ps, sig):
|
|
signal.signal(sig, partial(signal_handler_f, ps))
|
|
|
|
@classmethod
|
|
def disable_subprocess_signal_forwarding(cls, sig):
|
|
signal.signal(sig, signal.SIG_DFL)
|
|
|
|
@classmethod
|
|
def _log_command_exec(cls, cmd, stdin):
|
|
if stdin:
|
|
cls.logger.info('executing %s [%s]' % (cmd, stdin))
|
|
else:
|
|
cls.logger.info('executing %s' % cmd)
|
|
|
|
@classmethod
|
|
def _format_error(cls, cmd, cmd_returncode, cmd_output, stdin):
|
|
if type(cmd) is list:
|
|
cmd = ' '.join(cmd)
|
|
if stdin:
|
|
cmd = '%s [%s]' % (cmd, stdin)
|
|
if cmd_output:
|
|
return 'cmd \'%s\' failed: returned %d (%s)' % \
|
|
(cmd, cmd_returncode, cmd_output)
|
|
else:
|
|
return 'cmd \'%s\' failed: returned %d' % (cmd, cmd_returncode)
|
|
|
|
@classmethod
|
|
def _execute_subprocess(cls, cmd,
|
|
env=None,
|
|
shell=False,
|
|
close_fds=False,
|
|
stdout=True,
|
|
stdin=None,
|
|
stderr=subprocess.STDOUT):
|
|
"""
|
|
exec's commands using subprocess Popen
|
|
Args:
|
|
cmd, should be shlex.split if not shell
|
|
returns: output
|
|
|
|
Note: close_fds=True is affecting performance (2~3 times slower)
|
|
"""
|
|
if ifupdownflags.flags.DRYRUN:
|
|
return ''
|
|
|
|
cmd_output = None
|
|
try:
|
|
ch = subprocess.Popen(cmd,
|
|
env=env,
|
|
shell=shell,
|
|
close_fds=close_fds,
|
|
stdin=subprocess.PIPE if stdin else None,
|
|
stdout=subprocess.PIPE if stdout else cls.DEVNULL,
|
|
stderr=stderr)
|
|
utils.enable_subprocess_signal_forwarding(ch, signal.SIGINT)
|
|
if stdout or stdin:
|
|
cmd_output = ch.communicate(input=stdin)[0]
|
|
cmd_returncode = ch.wait()
|
|
except Exception as e:
|
|
raise Exception('cmd \'%s\' failed (%s)' % (' '.join(cmd), str(e)))
|
|
finally:
|
|
utils.disable_subprocess_signal_forwarding(signal.SIGINT)
|
|
if cmd_returncode != 0:
|
|
raise Exception(cls._format_error(cmd,
|
|
cmd_returncode,
|
|
cmd_output,
|
|
stdin))
|
|
return cmd_output
|
|
|
|
@classmethod
|
|
def exec_user_command(cls, cmd, close_fds=False, stdout=True,
|
|
stdin=None, stderr=subprocess.STDOUT):
|
|
cls._log_command_exec(cmd, stdin)
|
|
return cls._execute_subprocess(cmd,
|
|
shell=True,
|
|
close_fds=close_fds,
|
|
stdout=stdout,
|
|
stdin=stdin,
|
|
stderr=stderr)
|
|
|
|
@classmethod
|
|
def exec_command(cls, cmd, env=None, close_fds=False, stdout=True,
|
|
stdin=None, stderr=subprocess.STDOUT):
|
|
cls._log_command_exec(cmd, stdin)
|
|
return cls._execute_subprocess(shlex.split(cmd),
|
|
env=env,
|
|
close_fds=close_fds,
|
|
stdout=stdout,
|
|
stdin=stdin,
|
|
stderr=stderr)
|
|
|
|
@classmethod
|
|
def exec_commandl(cls, cmdl, env=None, close_fds=False, stdout=True,
|
|
stdin=None, stderr=subprocess.STDOUT):
|
|
cls._log_command_exec(' '.join(cmdl), stdin)
|
|
return cls._execute_subprocess(cmdl,
|
|
env=env,
|
|
close_fds=close_fds,
|
|
stdout=stdout,
|
|
stdin=stdin,
|
|
stderr=stderr)
|
|
|
|
fcntl.fcntl(utils.DEVNULL, fcntl.F_SETFD, fcntl.FD_CLOEXEC)
|