]> git.ipfire.org Git - thirdparty/lxc.git/commitdiff
conf: implement resource limits
authorWolfgang Bumiller <w.bumiller@proxmox.com>
Fri, 4 Nov 2016 09:19:07 +0000 (10:19 +0100)
committerWolfgang Bumiller <w.bumiller@proxmox.com>
Tue, 11 Apr 2017 12:01:11 +0000 (14:01 +0200)
This adds lxc.limit.<name> options consisting of one or two
colon separated numerical values (soft and optional hard
limit). If only one number is specified it'll be used for
both soft and hard limit. Additionally the word 'unlimited'
can be used instead of numbers.

Eg.
  lxc.limit.nofile = 30000:32768
  lxc.limit.stack = unlimited

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
configure.ac
src/lxc/attach.c
src/lxc/conf.c
src/lxc/conf.h
src/lxc/confile.c
src/lxc/start.c

index af04558275e9ac2b1d43e1466d596915fe491c15..287d57f559216cab1e2f2a4339f6bd9e9f99becb 100644 (file)
@@ -639,7 +639,7 @@ AC_CHECK_DECLS([PR_SET_NO_NEW_PRIVS], [], [], [#include <sys/prctl.h>])
 AC_CHECK_DECLS([PR_GET_NO_NEW_PRIVS], [], [], [#include <sys/prctl.h>])
 
 # Check for some headers
-AC_CHECK_HEADERS([sys/signalfd.h pty.h ifaddrs.h sys/memfd.h sys/personality.h utmpx.h sys/timerfd.h])
+AC_CHECK_HEADERS([sys/signalfd.h pty.h ifaddrs.h sys/memfd.h sys/personality.h utmpx.h sys/timerfd.h sys/resource.h])
 
 # lookup major()/minor()/makedev()
 AC_HEADER_MAJOR
index 9497148a3188b849c76a3200657ac76e37265a79..9683017603ed9acc4df9223aa556f0cabf39a5f8 100644 (file)
@@ -894,6 +894,11 @@ int lxc_attach(const char* name, const char* lxcpath, lxc_attach_exec_t exec_fun
                                goto on_error;
                }
 
+               /* Setup resource limits */
+               if (!lxc_list_empty(&init_ctx->container->lxc_conf->limits) && setup_resource_limits(&init_ctx->container->lxc_conf->limits, pid)) {
+                       goto on_error;
+               }
+
                /* Open /proc before setns() to the containers namespace so we
                 * don't rely on any information from inside the container.
                 */
index d0c51f659721720a93d763fc729c99a0f099329a..c8c2a5452fc93a006ebada993220016194db302d 100644 (file)
@@ -239,6 +239,11 @@ struct caps_opt {
        int value;
 };
 
+struct limit_opt {
+       char *name;
+       int value;
+};
+
 /*
  * The lxc_conf of the container currently being worked on in an
  * API call
@@ -371,6 +376,57 @@ static struct caps_opt caps_opt[] = {
 static struct caps_opt caps_opt[] = {};
 #endif
 
+static struct limit_opt limit_opt[] = {
+#ifdef RLIMIT_AS
+       { "as",          RLIMIT_AS          },
+#endif
+#ifdef RLIMIT_CORE
+       { "core",        RLIMIT_CORE        },
+#endif
+#ifdef RLIMIT_CPU
+       { "cpu",         RLIMIT_CPU         },
+#endif
+#ifdef RLIMIT_DATA
+       { "data",        RLIMIT_DATA        },
+#endif
+#ifdef RLIMIT_FSIZE
+       { "fsize",       RLIMIT_FSIZE       },
+#endif
+#ifdef RLIMIT_LOCKS
+       { "locks",       RLIMIT_LOCKS       },
+#endif
+#ifdef RLIMIT_MEMLOCK
+       { "memlock",     RLIMIT_MEMLOCK     },
+#endif
+#ifdef RLIMIT_MSGQUEUE
+       { "msgqueue",    RLIMIT_MSGQUEUE    },
+#endif
+#ifdef RLIMIT_NICE
+       { "nice",        RLIMIT_NICE        },
+#endif
+#ifdef RLIMIT_NOFILE
+       { "nofile",      RLIMIT_NOFILE      },
+#endif
+#ifdef RLIMIT_NPROC
+       { "nproc",       RLIMIT_NPROC       },
+#endif
+#ifdef RLIMIT_RSS
+       { "rss",         RLIMIT_RSS         },
+#endif
+#ifdef RLIMIT_RTPRIO
+       { "rtprio",      RLIMIT_RTPRIO      },
+#endif
+#ifdef RLIMIT_RTTIME
+       { "rttime",      RLIMIT_RTTIME      },
+#endif
+#ifdef RLIMIT_SIGPENDING
+       { "sigpending",  RLIMIT_SIGPENDING  },
+#endif
+#ifdef RLIMIT_STACK
+       { "stack",       RLIMIT_STACK       },
+#endif
+};
+
 static int run_buffer(char *buffer)
 {
        struct lxc_popen_FILE *f;
@@ -2473,6 +2529,45 @@ static int setup_network(struct lxc_list *network)
        return 0;
 }
 
+static int parse_resource(const char *res) {
+       size_t i;
+       int resid = -1;
+
+       for (i = 0; i < sizeof(limit_opt)/sizeof(limit_opt[0]); ++i) {
+               if (strcmp(res, limit_opt[i].name) == 0)
+                       return limit_opt[i].value;
+       }
+
+       /* try to see if it's numeric, so the user may specify
+        * resources that the running kernel knows about but
+        * we don't */
+       if (lxc_safe_int(res, &resid) == 0)
+               return resid;
+       return -1;
+}
+
+int setup_resource_limits(struct lxc_list *limits, pid_t pid) {
+       struct lxc_list *it;
+       struct lxc_limit *lim;
+       int resid;
+
+       lxc_list_for_each(it, limits) {
+               lim = it->elem;
+
+               resid = parse_resource(lim->resource);
+               if (resid < 0) {
+                       ERROR("unknown resource %s", lim->resource);
+                       return -1;
+               }
+
+               if (prlimit(pid, resid, &lim->limit, NULL) != 0) {
+                       ERROR("failed to set limit %s: %s", lim->resource, strerror(errno));
+                       return -1;
+               }
+       }
+       return 0;
+}
+
 /* try to move physical nics to the init netns */
 void lxc_restore_phys_nics_to_netns(int netnsfd, struct lxc_conf *conf)
 {
@@ -2559,6 +2654,7 @@ struct lxc_conf *lxc_conf_init(void)
        lxc_list_init(&new->includes);
        lxc_list_init(&new->aliens);
        lxc_list_init(&new->environment);
+       lxc_list_init(&new->limits);
        for (i=0; i<NUM_LXC_HOOKS; i++)
                lxc_list_init(&new->hooks[i]);
        lxc_list_init(&new->groups);
@@ -4178,6 +4274,31 @@ int lxc_clear_cgroups(struct lxc_conf *c, const char *key)
        return 0;
 }
 
+int lxc_clear_limits(struct lxc_conf *c, const char *key)
+{
+       struct lxc_list *it, *next;
+       bool all = false;
+       const char *k = NULL;
+
+       if (strcmp(key, "lxc.limit") == 0)
+               all = true;
+       else if (strncmp(key, "lxc.limit.", sizeof("lxc.limit.")-1) == 0)
+               k = key + sizeof("lxc.limit.")-1;
+       else
+               return -1;
+
+       lxc_list_for_each_safe(it, &c->limits, next) {
+               struct lxc_limit *lim = it->elem;
+               if (!all && strcmp(lim->resource, k) != 0)
+                       continue;
+               lxc_list_del(it);
+               free(lim->resource);
+               free(lim);
+               free(it);
+       }
+       return 0;
+}
+
 int lxc_clear_groups(struct lxc_conf *c)
 {
        struct lxc_list *it,*next;
@@ -4320,6 +4441,7 @@ void lxc_conf_free(struct lxc_conf *conf)
        lxc_clear_includes(conf);
        lxc_clear_aliens(conf);
        lxc_clear_environment(conf);
+       lxc_clear_limits(conf, "lxc.limit");
        free(conf);
 }
 
index b7d15cbd2e7a923816432b9b3eb9f29181cce42e..7c99048e4920e14a0f878c2e838ce100e71d2ba3 100644 (file)
@@ -30,6 +30,9 @@
 #include <net/if.h>
 #include <sys/param.h>
 #include <sys/types.h>
+#if HAVE_SYS_RESOURCE_H
+#include <sys/resource.h>
+#endif
 #include <stdbool.h>
 
 #include "list.h"
@@ -149,6 +152,23 @@ struct lxc_cgroup {
        char *value;
 };
 
+#if !HAVE_SYS_RESOURCE_H
+# define RLIM_INFINITY ((unsigned long)-1)
+struct rlimit {
+       unsigned long rlim_cur;
+       unsigned long rlim_max;
+};
+#endif
+/*
+ * Defines a structure to configure resource limits to set via setrlimit().
+ * @resource : the resource name in lowercase without the RLIMIT_ prefix
+ * @limit    : the limit to set
+ */
+struct lxc_limit {
+       char *resource;
+       struct rlimit limit;
+};
+
 enum idtype {
        ID_TYPE_UID,
        ID_TYPE_GID
@@ -385,6 +405,9 @@ struct lxc_conf {
 
        /* Whether PR_SET_NO_NEW_PRIVS will be set for the container. */
        bool no_new_privs;
+
+       /* RLIMIT_* limits */
+       struct lxc_list limits;
 };
 
 #ifdef HAVE_TLS
@@ -428,6 +451,7 @@ extern int lxc_clear_hooks(struct lxc_conf *c, const char *key);
 extern int lxc_clear_idmaps(struct lxc_conf *c);
 extern int lxc_clear_groups(struct lxc_conf *c);
 extern int lxc_clear_environment(struct lxc_conf *c);
+extern int lxc_clear_limits(struct lxc_conf *c, const char *key);
 extern int lxc_delete_autodev(struct lxc_handler *handler);
 
 extern int do_rootfs_setup(struct lxc_conf *conf, const char *name,
@@ -440,6 +464,8 @@ extern int do_rootfs_setup(struct lxc_conf *conf, const char *name,
 struct cgroup_process_info;
 extern int lxc_setup(struct lxc_handler *handler);
 
+extern int setup_resource_limits(struct lxc_list *limits, pid_t pid);
+
 extern void lxc_restore_phys_nics_to_netns(int netnsfd, struct lxc_conf *conf);
 
 extern int find_unmapped_nsuid(struct lxc_conf *conf, enum idtype idtype);
index db0aa33ec093a402e7ad48dccb5bc9505fbcddd2..7e2f265ae172a1985b56399257f982979012a75e 100644 (file)
@@ -121,6 +121,7 @@ static int config_init_uid(const char *, const char *, struct lxc_conf *);
 static int config_init_gid(const char *, const char *, struct lxc_conf *);
 static int config_ephemeral(const char *, const char *, struct lxc_conf *);
 static int config_no_new_privs(const char *, const char *, struct lxc_conf *);
+static int config_limit(const char *, const char *, struct lxc_conf *);
 
 static struct lxc_config_t config[] = {
 
@@ -195,6 +196,7 @@ static struct lxc_config_t config[] = {
        { "lxc.ephemeral",            config_ephemeral            },
        { "lxc.syslog",               config_syslog               },
        { "lxc.no_new_privs",         config_no_new_privs         },
+       { "lxc.limit",                config_limit                },
 };
 
 struct signame {
@@ -1580,6 +1582,110 @@ out:
        return -1;
 }
 
+static bool parse_limit_value(const char **value, unsigned long *res) {
+       char *endptr = NULL;
+
+       if (strncmp(*value, "unlimited", sizeof("unlimited")-1) == 0) {
+               *res = RLIM_INFINITY;
+               *value += sizeof("unlimited")-1;
+               return true;
+       }
+
+       errno = 0;
+       *res = strtoul(*value, &endptr, 10);
+       if (errno || !endptr)
+               return false;
+       *value = endptr;
+
+       return true;
+}
+
+static int config_limit(const char *key, const char *value,
+                        struct lxc_conf *lxc_conf)
+{
+       struct lxc_list *limlist = NULL;
+       struct lxc_limit *limelem = NULL;
+       struct lxc_list *iter;
+       struct rlimit limit;
+       unsigned long limit_value;
+
+       if (!value || strlen(value) == 0)
+               return lxc_clear_limits(lxc_conf, key);
+
+       if (strncmp(key, "lxc.limit.", sizeof("lxc.limit.")-1) != 0)
+               return -1;
+
+       key += sizeof("lxc.limit.")-1;
+
+       /* soft limit comes first in the value */
+       if (!parse_limit_value(&value, &limit_value))
+               return -1;
+       limit.rlim_cur = limit_value;
+
+       /* skip spaces and a colon */
+       while (isspace(*value))
+               ++value;
+       if (*value == ':')
+               ++value;
+       else if (*value) /* any other character is an error here */
+               return -1;
+       while (isspace(*value))
+               ++value;
+
+       /* optional hard limit */
+       if (*value) {
+               if (!parse_limit_value(&value, &limit_value))
+                       return -1;
+               limit.rlim_max = limit_value;
+               /* check for trailing garbage */
+               while (isspace(*value))
+                       ++value;
+               if (*value)
+                       return -1;
+       } else {
+               /* a single value sets both hard and soft limit */
+               limit.rlim_max = limit.rlim_cur;
+       }
+
+       /* find existing list element */
+       lxc_list_for_each(iter, &lxc_conf->limits) {
+               limelem = iter->elem;
+               if (!strcmp(key, limelem->resource)) {
+                       limelem->limit = limit;
+                       return 0;
+               }
+       }
+
+       /* allocate list element */
+       limlist = malloc(sizeof(*limlist));
+       if (!limlist)
+               goto out;
+       
+       limelem = malloc(sizeof(*limelem));
+       if (!limelem)
+               goto out;
+       memset(limelem, 0, sizeof(*limelem));
+
+       limelem->resource = strdup(key);
+       if (!limelem->resource)
+               goto out;
+       limelem->limit = limit;
+
+       limlist->elem = limelem;
+
+       lxc_list_add_tail(&lxc_conf->limits, limlist);
+
+       return 0;
+
+out:
+       free(limlist);
+       if (limelem) {
+               free(limelem->resource);
+               free(limelem);
+       }
+       return -1;
+}
+
 static int config_idmap(const char *key, const char *value, struct lxc_conf *lxc_conf)
 {
        char *token = "lxc.id_map";
@@ -2313,6 +2419,55 @@ static int lxc_get_cgroup_entry(struct lxc_conf *c, char *retv, int inlen,
        return fulllen;
 }
 
+/*
+ * If you ask for a specific value, i.e. lxc.limit.nofile, then just the value
+ * will be printed. If you ask for 'lxc.limit', then all limit entries will be
+ * printed, in 'lxc.limit.resource = value' format.
+ */
+static int lxc_get_limit_entry(struct lxc_conf *c, char *retv, int inlen,
+                               const char *key)
+{
+       int fulllen = 0, len;
+       int all = 0;
+       struct lxc_list *it;
+
+       if (!retv)
+               inlen = 0;
+       else
+               memset(retv, 0, inlen);
+
+       if (strcmp(key, "all") == 0)
+               all = 1;
+
+       lxc_list_for_each(it, &c->limits) {
+               char buf[LXC_NUMSTRLEN64*2+2]; /* 2 colon separated 64 bit integers or the word 'unlimited' */
+               int partlen;
+               struct lxc_limit *lim = it->elem;
+
+               if (lim->limit.rlim_cur == RLIM_INFINITY) {
+                       memcpy(buf, "unlimited", sizeof("unlimited"));
+                       partlen = sizeof("unlimited")-1;
+               } else {
+                       partlen = sprintf(buf, "%lu", lim->limit.rlim_cur);
+               }
+               if (lim->limit.rlim_cur != lim->limit.rlim_max) {
+                       if (lim->limit.rlim_max == RLIM_INFINITY) {
+                               memcpy(buf+partlen, ":unlimited", sizeof(":unlimited"));
+                       } else {
+                               sprintf(buf+partlen, ":%lu", lim->limit.rlim_max);
+                       }
+               }
+
+               if (all) {
+                       strprint(retv, inlen, "lxc.limit.%s = %s\n", lim->resource, buf);
+               } else if (strcmp(lim->resource, key) == 0) {
+                       strprint(retv, inlen, "%s", buf);
+               }
+       }
+
+       return fulllen;
+}
+
 static int lxc_get_item_hooks(struct lxc_conf *c, char *retv, int inlen,
                              const char *key)
 {
@@ -2678,6 +2833,10 @@ int lxc_get_config_item(struct lxc_conf *c, const char *key, char *retv,
                v = c->syslog;
        else if (strcmp(key, "lxc.no_new_privs") == 0)
                return lxc_get_conf_int(c, retv, inlen, c->no_new_privs);
+       else if (strcmp(key, "lxc.limit") == 0) // all limits
+               return lxc_get_limit_entry(c, retv, inlen, "all");
+       else if (strncmp(key, "lxc.limit.", 10) == 0) // specific limit
+               return lxc_get_limit_entry(c, retv, inlen, key + 10);
        else return -1;
 
        if (!v)
@@ -2711,6 +2870,8 @@ int lxc_clear_config_item(struct lxc_conf *c, const char *key)
                return lxc_clear_environment(c);
        else if (strncmp(key, "lxc.id_map", 10) == 0)
                return lxc_clear_idmaps(c);
+       else if (strncmp(key, "lxc.limit", 9) == 0)
+               return lxc_clear_limits(c, key);
        return -1;
 }
 
index e586881a9132069f638f77baf80f0ef9fb05151b..fa1ade274d73963e287d032d81078cbc5ee629c5 100644 (file)
@@ -1261,6 +1261,11 @@ static int lxc_spawn(struct lxc_handler *handler)
        if (lxc_sync_barrier_child(handler, LXC_SYNC_POST_CONFIGURE))
                goto out_delete_net;
 
+       if (!lxc_list_empty(&handler->conf->limits) && setup_resource_limits(&handler->conf->limits, handler->pid)) {
+               ERROR("failed to setup resource limits for '%s'", name);
+               return -1;
+       }
+
        if (!cgroup_setup_limits(handler, true)) {
                ERROR("Failed to setup the devices cgroup for container \"%s\".", name);
                goto out_delete_net;