from . import _pakfire
from . import base
-from . import cgroups
from . import config
from . import logger
from . import util
if not _pakfire.arch_supported_by_host(self.arch):
raise BuildError(_("Cannot build for %s on this host") % self.arch)
- # Initialize cgroups
- self.cgroup = self._make_cgroup()
-
def __enter__(self):
self.log.debug("Entering %s" % self)
def __exit__(self, type, value, traceback):
self.log.debug("Leaving %s" % self)
- # Kill all remaining processes in the build environment
- self.cgroup.killall()
-
- # Destroy the cgroup
- self.cgroup.destroy()
- self.cgroup = None
-
def setup_logging(self, logfile):
l = log.getChild(self.build_id)
l.setLevel(logging.DEBUG)
return l
- def _make_cgroup(self):
- """
- Initialises a cgroup so that we can enforce resource limits
- and can identify processes belonging to this build environment.
- """
- # Find our current group
- parent = cgroups.get_own_group()
-
- # Create a sub-group
- cgroup = parent.create_subgroup("pakfire-%s" % self.build_id)
-
- # Make this process join the new group
- cgroup.attach_self()
-
- return cgroup
-
class BuilderContext(object):
def __init__(self, pakfire, builder):
+++ /dev/null
-#!/usr/bin/python3
-###############################################################################
-# #
-# Pakfire - The IPFire package management system #
-# Copyright (C) 2021 Pakfire development team #
-# #
-# This program 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. #
-# #
-# This program 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 this program. If not, see <http://www.gnu.org/licenses/>. #
-# #
-###############################################################################
-
-import logging
-import os
-import signal
-import time
-
-log = logging.getLogger("pakfire.cgroups")
-
-def find_group_by_pid(pid):
- """
- Returns the cgroup of the process currently running with pid
- """
- with open("/proc/%s/cgroup" % pid) as f:
- for line in f:
- if not line.startswith("0::"):
- continue
-
- # Clean up path
- path = line[3:].rstrip()
-
- return CGroup(path)
-
-def get_own_group():
- """
- Returns the cgroup of the process we are currently in
- """
- pid = os.getpid()
-
- return find_group_by_pid(pid)
-
-class CGroup(object):
- """
- cgroup controller
- """
- root = "/sys/fs/cgroup/unified"
-
- def __init__(self, path):
- if not path.startswith("/"):
- raise ValueError("Invalid cgroup path")
-
- # Store path
- self.path = path
-
- # Make absolute path
- self.abspath = "%s%s" % (self.root, self.path)
-
- if not os.path.isdir(self.abspath):
- raise ValueError("Non-existant cgroup")
-
- def __repr__(self):
- return "<%s path=%s>" % (self.__class__.__name__, self.path)
-
- def _open(self, path, mode="r"):
- """
- Opens a file in this cgroup for reading of writing
- """
- # Make full path
- path = os.path.join(self.abspath, path)
-
- return open(path, mode)
-
- @property
- def parent(self):
- """
- Returns the parent group
- """
- return self.__class__(os.path.dirname(self.path))
-
- def create_subgroup(self, name):
- path = os.path.join(self.path, name)
-
- # Create directory
- try:
- os.mkdir("%s%s" % (self.root, path))
-
- log.debug("New cgroup '%s' created" % path)
-
- # Silently continue if groups already exists
- except FileExistsError:
- pass
-
- # Return new instance
- return self.__class__(path)
-
- def destroy(self):
- """
- Destroys this cgroup
- """
- log.debug("Destroying cgroup %s" % self.path)
-
- # Move whatever is left to the parent group
- self.migrate(self.parent)
-
- # Remove the file tree
- try:
- os.rmdir(self.abspath)
- except OSError as e:
- # Ignore "Device or resource busy".
- if e.errno == 16:
- return
-
- raise
-
- @property
- def pids(self):
- """
- Returns the PIDs of all currently in this group running processes
- """
- pids = []
-
- with self._open("cgroup.procs") as f:
- for line in f:
- try:
- pid = int(line)
- except (TypeError, ValueError):
- pass
-
- pids.append(pid)
-
- return pids
-
- def attach_process(self, pid):
- """
- Attaches the process PID to this group
- """
- log.debug("Attaching process %s to group %s" % (pid, self.path))
-
- with self._open("cgroup.procs", "w") as f:
- f.write("%s\n" % pid)
-
- def attach_self(self):
- """
- Attaches this process to the group
- """
- return self.attach_process(os.getpid())
-
- def detach_self(self):
- pid = os.getpid()
-
- # Move process to parent
- if pid in self.pids:
- self.parent.attach_process(pid)
-
- def migrate(self, group):
- """
- Migrates all processes to the given group
- """
- for pid in self.pids:
- group.attach_process(pid)
-
- def _kill(self, signal=signal.SIGTERM):
- """
- Sends signal to all processes in this cgroup
- """
- for pid in self.pids:
- log.debug("Sending signal %s to process %s" % (signal, pid))
-
- try:
- os.kill(pid, signal)
- except OSError as e:
- # Skip "No such process" error
- if e.errno == 3:
- pass
- else:
- raise
-
- # Return True if there are any processes left
- return not self.pids
-
- def killall(self, timeout=10):
- """
- Kills all processes
- """
- self.detach_self()
-
- for i in range(timeout * 10):
- if i >= 10:
- s = signal.SIGKILL
- else:
- s = signal.SIGTERM
-
- # Send signal and end loop when no processes are left
- if self._kill(signal=s):
- break
-
- # Sleep for 100ms
- time.sleep(0.1)
+++ /dev/null
-#!/usr/bin/python3
-
-import os
-import unittest
-
-import pakfire.cgroups as cgroups
-
-class Test(unittest.TestCase):
- def setUp(self):
- # Find our own cgroup
- self.cgroup = cgroups.get_own_group()
-
- def test_find_own_group(self):
- """
- Check if we found our own cgroup
- """
- self.assertIsInstance(self.cgroup, cgroups.CGroup)
-
- def test_subgroup(self):
- # Create a new sub group
- subgroup = self.cgroup.create_subgroup("test-1")
- self.assertIsInstance(subgroup, cgroups.CGroup)
-
- # Attach the test process to it
- subgroup.attach_self()
-
- # Fetch pids
- pids = subgroup.pids
-
- # There must be one pid in this list
- self.assertTrue(len(pids) == 1)
-
- # The pid must be the one of this process
- self.assertTrue(pids[0] == os.getpid())
-
- # Can't really test killing ourselves here
- #subgroup.killall()
-
- # Destroy it
- subgroup.destroy()
-
-
-if __name__ == "__main__":
- unittest.main()