Don't clean stuff up if it's already running.
[util-vserver-pl.git] / python / vserver.py
index e3748f4..aee6d03 100644 (file)
@@ -129,6 +129,26 @@ class VServerConfig:
         os.path.walk(self.dir, add_to_cache, self.cache)
 
 
         os.path.walk(self.dir, add_to_cache, self.cache)
 
 
+def adjust_lim(goal, curr):
+    gh = goal[0]
+    gs = goal[1]
+    gm = goal[2]
+    soft = curr[0]
+    hard = curr[1]
+    if gm != VC_LIM_KEEP:
+        if gm > soft or gm == resource.RLIM_INFINITY:
+            soft = gm
+        if gm > hard or gm == resource.RLIM_INFINITY:
+            hard = gm
+    if gs != VC_LIM_KEEP:
+        if gs > soft or gs == resource.RLIM_INFINITY:
+            soft = gs
+    if gh != VC_LIM_KEEP:
+        if gh > hard or gh == resource.RLIM_INFINITY:
+            hard = gh
+    return (soft, hard)
+
+
 class VServer:
 
     INITSCRIPTS = [('/etc/rc.vinit', 'start'),
 class VServer:
 
     INITSCRIPTS = [('/etc/rc.vinit', 'start'),
@@ -181,24 +201,49 @@ class VServer:
             resource_type = RLIMITS[type]
             try:
                 vserverimpl.setrlimit(self.ctx, resource_type, hard, soft, min)
             resource_type = RLIMITS[type]
             try:
                 vserverimpl.setrlimit(self.ctx, resource_type, hard, soft, min)
+                if hasattr(resource, 'RLIMIT_' + type):
+                    lim = resource.getrlimit(resource_type)
+                    lim = adjust_lim((hard, soft, min), lim)
+                    resource.setrlimit(resource_type, lim)
             except OSError, e:
                 self.log("Error: setrlimit(%d, %s, %d, %d, %d): %s"
                          % (self.ctx, type.lower(), hard, soft, min))
 
         return update
 
             except OSError, e:
                 self.log("Error: setrlimit(%d, %s, %d, %d, %d): %s"
                          % (self.ctx, type.lower(), hard, soft, min))
 
         return update
 
-    def set_capabilities(self, capabilities):
-        return vserverimpl.setbcaps(self.ctx, vserverimpl.text2bcaps(capabilities))
+    def get_prefix_from_capabilities(self, capabilities, prefix):
+        split_caps = capabilities.split(',')
+        return ",".join(["%s" % (c) for c in split_caps if c.startswith(prefix.upper()) or c.startswith(prefix.lower())])
+
+    def get_bcaps_from_capabilities(self, capabilities):
+        return self.get_prefix_from_capabilities(capabilities, "cap_")
+
+    def get_ccaps_from_capabilities(self, capabilities):
+        return self.get_prefix_from_capabilities(capabilities, "vxc_")
 
     def set_capabilities_config(self, capabilities):
 
     def set_capabilities_config(self, capabilities):
-        self.config.update('bcapabilities', capabilities)
-        self.set_capabilities(capabilities)
+        bcaps = self.get_bcaps_from_capabilities(capabilities)
+        ccaps = self.get_ccaps_from_capabilities(capabilities)
+        self.config.update('bcapabilities', bcaps)
+        self.config.update('ccapabilities', ccaps)
+        ret = vserverimpl.setbcaps(self.ctx, vserverimpl.text2bcaps(bcaps))
+        if ret > 0:
+            return ret
+        return vserverimpl.setccaps(self.ctx, vserverimpl.text2ccaps(ccaps))
 
     def get_capabilities(self):
 
     def get_capabilities(self):
-        return vserverimpl.bcaps2text(vserverimpl.getbcaps(self.ctx))
+        bcaps = vserverimpl.bcaps2text(vserverimpl.getbcaps(self.ctx))
+        ccaps = vserverimpl.ccaps2text(vserverimpl.getccaps(self.ctx))
+        if bcaps and ccaps:
+            ccaps = "," + ccaps
+        return (bcaps + ccaps)
  
     def get_capabilities_config(self):
  
     def get_capabilities_config(self):
-        return self.config.get('bcapabilities', '')
+        bcaps = self.config.get('bcapabilities', '')
+        ccaps = self.config.get('ccapabilities', '')
+        if bcaps and ccaps:
+            ccaps = "," + ccaps
+        return (bcaps + ccaps)
 
     def set_ipaddresses(self, addresses):
         vserverimpl.netremove(self.ctx, "all")
 
     def set_ipaddresses(self, addresses):
         vserverimpl.netremove(self.ctx, "all")
@@ -256,7 +301,7 @@ class VServer:
             try:
                 vserverimpl.unsetdlimit(self.dir, self.ctx)
             except OSError, e:
             try:
                 vserverimpl.unsetdlimit(self.dir, self.ctx)
             except OSError, e:
-                self.log("Unexpected error with unsetdlimit for context %d" % self.ctx)
+                self.log("Unexpected error with unsetdlimit for context %d: %r" % (self.ctx,e))
             return
 
         if self.vm_running:
             return
 
         if self.vm_running:
@@ -267,7 +312,6 @@ class VServer:
             block_usage = self.disk_blocks
             inode_usage = self.disk_inodes
 
             block_usage = self.disk_blocks
             inode_usage = self.disk_inodes
 
-
         try:
             vserverimpl.setdlimit(self.dir,
                                   self.ctx,
         try:
             vserverimpl.setdlimit(self.dir,
                                   self.ctx,
@@ -277,7 +321,7 @@ class VServer:
                                   vserverimpl.DLIMIT_INF,  # inode limit
                                   2)   # %age reserved for root
         except OSError, e:
                                   vserverimpl.DLIMIT_INF,  # inode limit
                                   2)   # %age reserved for root
         except OSError, e:
-            self.log("Unexpected error with setdlimit for context %d" % self.ctx)
+            self.log("Unexpected error with setdlimit for context %d: %r" % (self.ctx, e))
 
 
         self.config.update('dlimits/0/space_total', block_limit)
 
 
         self.config.update('dlimits/0/space_total', block_limit)
@@ -349,40 +393,47 @@ class VServer:
             state_file.close()
 
         if vserverimpl.chcontext(self.ctx, vserverimpl.text2bcaps(self.get_capabilities_config())):
             state_file.close()
 
         if vserverimpl.chcontext(self.ctx, vserverimpl.text2bcaps(self.get_capabilities_config())):
-            self.set_resources()
+            self.set_resources(True)
             vserverimpl.setup_done(self.ctx)
 
             vserverimpl.setup_done(self.ctx)
 
+
     def __prep(self, runlevel):
 
         """ Perform all the crap that the vserver script does before
         actually executing the startup scripts. """
 
     def __prep(self, runlevel):
 
         """ Perform all the crap that the vserver script does before
         actually executing the startup scripts. """
 
-        # remove /var/run and /var/lock/subsys files
-        # but don't remove utmp from the top-level /var/run
-        RUNDIR = "/var/run"
-        LOCKDIR = "/var/lock/subsys"
-        filter_fn = lambda fs: filter(lambda f: f != 'utmp', fs)
-        garbage = reduce((lambda (out, ff), (dir, subdirs, files):
-                          (out + map((dir + "/").__add__, ff(files)),
-                           lambda fs: fs)),
-                         list(os.walk(RUNDIR)),
-                         ([], filter_fn))[0]
-        garbage += filter(os.path.isfile, map((LOCKDIR + "/").__add__,
-                                              os.listdir(LOCKDIR)))
-        if False:
-            for f in garbage:
-                os.unlink(f)
 
         # set the initial runlevel
 
         # set the initial runlevel
-        vserverimpl.setrunlevel(RUNDIR + "/utmp", runlevel)
+        vserverimpl.setrunlevel(self.dir + "/var/run/utmp", runlevel)
 
         # mount /proc and /dev/pts
         self.__do_mount("none", self.dir, "/proc", "proc")
         # XXX - magic mount options
         self.__do_mount("none", self.dir, "/dev/pts", "devpts", 0, "gid=5,mode=0620")
 
 
         # mount /proc and /dev/pts
         self.__do_mount("none", self.dir, "/proc", "proc")
         # XXX - magic mount options
         self.__do_mount("none", self.dir, "/dev/pts", "devpts", 0, "gid=5,mode=0620")
 
-    def __do_mount(self, *mount_args):
 
 
+    def __cleanvar(self):
+        """
+        Clean the /var/ directory so RH startup scripts can run
+        """ 
+
+        RUNDIR = "/var/run"
+        LOCKDIR = "/var/lock/subsys"
+
+        filter = ["utmp"]
+        garbage = []
+        for topdir in [RUNDIR, LOCKDIR]:
+            #os.walk() = (dirpath, dirnames, filenames)
+            for root, dirs, files in os.walk(topdir):
+                for file in files:
+                    if not file in filter:
+                        garbage.append(root + "/" + file)
+
+        for f in garbage: os.unlink(f)
+        return garbage
+
+
+    def __do_mount(self, *mount_args):
         try:
             vserverimpl.mount(*mount_args)
         except OSError, ex:
         try:
             vserverimpl.mount(*mount_args)
         except OSError, ex:
@@ -391,25 +442,32 @@ class VServer:
                 return
             raise ex
 
                 return
             raise ex
 
+
     def enter(self):
         self.config.cache_it()
         self.__do_chroot()
         self.__do_chcontext(None)
 
     def enter(self):
         self.config.cache_it()
         self.__do_chroot()
         self.__do_chcontext(None)
 
-    def start(self, wait, runlevel = 3):
-        self.vm_running = True
 
 
-        child_pid = os.fork()
-        if child_pid == 0:
+    def start(self, runlevel = 3):
+
+        if (os.fork() != 0):
+            # Parent should just return.
+            self.vm_running = True
+            return
+        else:
             # child process
             try:
             # child process
             try:
+                # so we don't chcontext with priv'ed fds
+                close_nonstandard_fds()
+
                 # get a new session
                 os.setsid()
 
                 # open state file to record vserver info
                 state_file = open("/var/run/vservers/%s" % self.name, "w")
 
                 # get a new session
                 os.setsid()
 
                 # open state file to record vserver info
                 state_file = open("/var/run/vservers/%s" % self.name, "w")
 
-                # use /dev/null for stdin, /var/log/boot.log for stdout/err
+                # use /dev/null for stdin, /var/log/nm for stdout/err
                 fd = os.open("/dev/null", os.O_RDONLY)
                 if fd != 0:
                     os.dup2(fd, 0)
                 fd = os.open("/dev/null", os.O_RDONLY)
                 if fd != 0:
                     os.dup2(fd, 0)
@@ -420,37 +478,41 @@ class VServer:
 
                 self.config.cache_it()
                 self.__do_chroot()
 
                 self.config.cache_it()
                 self.__do_chroot()
-                log = open("/var/log/boot.log", "a", 0)
+                if not self.is_running():
+                    removed = self.__cleanvar()
+
+                log = open("/var/log/nm", "a", 0)
                 if log.fileno() != 1:
                     os.dup2(log.fileno(), 1)
                 os.dup2(1, 2)
 
                 if log.fileno() != 1:
                     os.dup2(log.fileno(), 1)
                 os.dup2(1, 2)
 
+                print >>log, ("%s: removing %s" % 
+                                (time.asctime(time.gmtime()), removed))
                 print >>log, ("%s: starting the virtual server %s" %
                 print >>log, ("%s: starting the virtual server %s" %
-                              (time.asctime(time.gmtime()), self.name))
+                                (time.asctime(time.gmtime()), self.name))
                 # execute each init script in turn
                 # XXX - we don't support all scripts that vserver script does
                 self.__do_chcontext(state_file)
                 for cmd in self.INITSCRIPTS:
                 # execute each init script in turn
                 # XXX - we don't support all scripts that vserver script does
                 self.__do_chcontext(state_file)
                 for cmd in self.INITSCRIPTS:
-                     try:
-                         # enter vserver context
-                         arg_subst = { 'runlevel': runlevel }
-                         cmd_args = [cmd[0]] + map(lambda x: x % arg_subst,
+                    try:
+                        # enter vserver context
+                        arg_subst = { 'runlevel': runlevel }
+                        cmd_args = [cmd[0]] + map(lambda x: x % arg_subst,
                                                    cmd[1:])
                                                    cmd[1:])
-                         print >>log, "executing '%s'" % " ".join(cmd_args)
-                         os.spawnvp(os.P_NOWAIT,cmd[0],cmd_args)
-                     except:
-                         print >>log, traceback.format_exc()
-                         os._exit(1)
+                        if os.path.isfile(cmd[0]):                         
+                            print >>log, "executing '%s'" % " ".join(cmd_args)
+                            os.spawnvp(os.P_NOWAIT,cmd[0],cmd_args)
+                        else:
+                            print >>log, "WARNING: could not run %s"%cmd[0]
+                    except:
+                        print >>log, traceback.format_exc()
 
             # we get here due to an exception in the top-level child process
             except Exception, ex:
                 self.log(traceback.format_exc())
             os._exit(0)
 
 
             # we get here due to an exception in the top-level child process
             except Exception, ex:
                 self.log(traceback.format_exc())
             os._exit(0)
 
-        # parent process
-        return child_pid
-
-    def set_resources(self):
+    def set_resources(self,setup=False):
 
         """ Called when vserver context is entered for first time,
         should be overridden by subclass. """
 
         """ Called when vserver context is entered for first time,
         should be overridden by subclass. """
@@ -458,6 +520,13 @@ class VServer:
         pass
 
     def init_disk_info(self):
         pass
 
     def init_disk_info(self):
+        try:
+            dlimit = vserverimpl.getdlimit(self.dir, self.ctx)
+            self.disk_blocks = dlimit[0]
+            self.disk_inodes = dlimit[2]
+            return self.disk_blocks * 1024
+        except Exception, e:
+            pass
         cmd = "/usr/sbin/vdu --script --space --inodes --blocksize 1024 --xid %d %s" % (self.ctx, self.dir)
         p = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE,
                              stdout=subprocess.PIPE, stderr=subprocess.PIPE,
         cmd = "/usr/sbin/vdu --script --space --inodes --blocksize 1024 --xid %d %s" % (self.ctx, self.dir)
         p = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE,
                              stdout=subprocess.PIPE, stderr=subprocess.PIPE,
@@ -506,3 +575,12 @@ def create(vm_name, static = False, ctor = VServer):
     vm_id = pwd.getpwnam(vm_name)[2]
 
     return ctor(vm_name, vm_id)
     vm_id = pwd.getpwnam(vm_name)[2]
 
     return ctor(vm_name, vm_id)
+
+
+def close_nonstandard_fds():
+    """Close all open file descriptors other than 0, 1, and 2."""
+    _SC_OPEN_MAX = 4
+    for fd in range(3, os.sysconf(_SC_OPEN_MAX)):
+        try: os.close(fd)
+        except OSError: pass  # most likely an fd that isn't open
+