Unverified Commit b2ff0ccc by Stéphane Graber Committed by GitHub

Merge pull request #3461 from brauner/2020-06-25/time_namespace

time namespace support
parents f0a3c722 7fb5a8df
...@@ -122,3 +122,8 @@ When running on kernels that support pidfds LXC will rely on them for most opera ...@@ -122,3 +122,8 @@ When running on kernels that support pidfds LXC will rely on them for most opera
## cgroup\_advanced\_isolation ## cgroup\_advanced\_isolation
Privileged containers will usually be able to override the cgroup limits given to them. This introduces three new configuration keys `lxc.cgroup.dir.monitor`, `lxc.cgroup.dir.container`, and `lxc.cgroup.dir.container.inner`. The `lxc.cgroup.dir.monitor` and `lxc.cgroup.dir.container` keys can be used to set to place the `monitor` and the `container` into different cgroups. The `lxc.cgroup.dir.container.inner` key can be set to a cgroup that is concatenated with `lxc.cgroup.dir.container`. When `lxc.cgroup.dir.container.inner` is set the container will be placed into the `lxc.cgroup.dir.container.inner` cgroup but the limits will be set in the `lxc.cgroup.dir.container` cgroup. This way privileged containers cannot escape their cgroup limits. Privileged containers will usually be able to override the cgroup limits given to them. This introduces three new configuration keys `lxc.cgroup.dir.monitor`, `lxc.cgroup.dir.container`, and `lxc.cgroup.dir.container.inner`. The `lxc.cgroup.dir.monitor` and `lxc.cgroup.dir.container` keys can be used to set to place the `monitor` and the `container` into different cgroups. The `lxc.cgroup.dir.container.inner` key can be set to a cgroup that is concatenated with `lxc.cgroup.dir.container`. When `lxc.cgroup.dir.container.inner` is set the container will be placed into the `lxc.cgroup.dir.container.inner` cgroup but the limits will be set in the `lxc.cgroup.dir.container` cgroup. This way privileged containers cannot escape their cgroup limits.
## time\_namespace
This adds time namespace support to LXC.
...@@ -1806,6 +1806,33 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA ...@@ -1806,6 +1806,33 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
</para> </para>
</listitem> </listitem>
</varlistentry> </varlistentry>
<varlistentry>
<term>
<option>lxc.time.offset.boot</option>
</term>
<listitem>
<para>
Specify a positive or negative offset for the boottime clock. The
format accepts hours (h), minutes (m), seconds (s),
milliseconds (ms), microseconds (us), and nanoseconds (ns).
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>
<option>lxc.time.offset.monotonic</option>
</term>
<listitem>
<para>
Specify a positive or negative offset for the montonic clock. The
format accepts hours (h), minutes (m), seconds (s),
milliseconds (ms), microseconds (us), and nanoseconds (ns).
</para>
</listitem>
</varlistentry>
</variablelist> </variablelist>
</refsect2> </refsect2>
......
...@@ -41,6 +41,7 @@ static char *api_extensions[] = { ...@@ -41,6 +41,7 @@ static char *api_extensions[] = {
"pidfd", "pidfd",
"cgroup_advanced_isolation", "cgroup_advanced_isolation",
"network_bridge_vlan", "network_bridge_vlan",
"time_namespace",
}; };
static size_t nr_api_extensions = sizeof(api_extensions) / sizeof(*api_extensions); static size_t nr_api_extensions = sizeof(api_extensions) / sizeof(*api_extensions);
......
...@@ -2599,6 +2599,7 @@ struct lxc_conf *lxc_conf_init(void) ...@@ -2599,6 +2599,7 @@ struct lxc_conf *lxc_conf_init(void)
new->init_gid = 0; new->init_gid = 0;
memset(&new->cgroup_meta, 0, sizeof(struct lxc_cgroup)); memset(&new->cgroup_meta, 0, sizeof(struct lxc_cgroup));
memset(&new->ns_share, 0, sizeof(char *) * LXC_NS_MAX); memset(&new->ns_share, 0, sizeof(char *) * LXC_NS_MAX);
memset(&new->timens, 0, sizeof(struct timens_offsets));
seccomp_conf_init(new); seccomp_conf_init(new);
return new; return new;
......
...@@ -233,6 +233,16 @@ struct device_item { ...@@ -233,6 +233,16 @@ struct device_item {
int global_rule; int global_rule;
}; };
struct timens_offsets {
/* Currently, either s_boot or ns_boot is set, but not both. */
int64_t s_boot;
int64_t ns_boot;
/* Currently, either s_monotonic or ns_monotonic is set, but not both. */
int64_t s_monotonic;
int64_t ns_monotonic;
};
struct lxc_conf { struct lxc_conf {
/* Pointer to the name of the container. Do not free! */ /* Pointer to the name of the container. Do not free! */
const char *name; const char *name;
...@@ -401,6 +411,8 @@ struct lxc_conf { ...@@ -401,6 +411,8 @@ struct lxc_conf {
/* Absolute path (in the container) to the shared mount point */ /* Absolute path (in the container) to the shared mount point */
char *path_cont; char *path_cont;
} shmount; } shmount;
struct timens_offsets timens;
}; };
extern int write_id_mapping(enum idtype idtype, pid_t pid, const char *buf, extern int write_id_mapping(enum idtype idtype, pid_t pid, const char *buf,
......
...@@ -104,6 +104,8 @@ lxc_config_define(mount_auto); ...@@ -104,6 +104,8 @@ lxc_config_define(mount_auto);
lxc_config_define(mount_fstab); lxc_config_define(mount_fstab);
lxc_config_define(namespace_clone); lxc_config_define(namespace_clone);
lxc_config_define(namespace_keep); lxc_config_define(namespace_keep);
lxc_config_define(time_offset_boot);
lxc_config_define(time_offset_monotonic);
lxc_config_define(namespace_share); lxc_config_define(namespace_share);
lxc_config_define(net); lxc_config_define(net);
lxc_config_define(net_flags); lxc_config_define(net_flags);
...@@ -177,7 +179,7 @@ static struct lxc_config_t config_jump_table[] = { ...@@ -177,7 +179,7 @@ static struct lxc_config_t config_jump_table[] = {
{ "lxc.cap.keep", set_config_cap_keep, get_config_cap_keep, clr_config_cap_keep, }, { "lxc.cap.keep", set_config_cap_keep, get_config_cap_keep, clr_config_cap_keep, },
{ "lxc.cgroup2", set_config_cgroup2_controller, get_config_cgroup2_controller, clr_config_cgroup2_controller, }, { "lxc.cgroup2", set_config_cgroup2_controller, get_config_cgroup2_controller, clr_config_cgroup2_controller, },
{ "lxc.cgroup.dir.monitor", set_config_cgroup_monitor_dir, get_config_cgroup_monitor_dir, clr_config_cgroup_monitor_dir, }, { "lxc.cgroup.dir.monitor", set_config_cgroup_monitor_dir, get_config_cgroup_monitor_dir, clr_config_cgroup_monitor_dir, },
{ "lxc.cgroup.dir.container.inner",set_config_cgroup_container_inner_dir, get_config_cgroup_container_inner_dir, clr_config_cgroup_container_inner_dir,}, { "lxc.cgroup.dir.container.inner", set_config_cgroup_container_inner_dir, get_config_cgroup_container_inner_dir, clr_config_cgroup_container_inner_dir, },
{ "lxc.cgroup.dir.container", set_config_cgroup_container_dir, get_config_cgroup_container_dir, clr_config_cgroup_container_dir, }, { "lxc.cgroup.dir.container", set_config_cgroup_container_dir, get_config_cgroup_container_dir, clr_config_cgroup_container_dir, },
{ "lxc.cgroup.dir", set_config_cgroup_dir, get_config_cgroup_dir, clr_config_cgroup_dir, }, { "lxc.cgroup.dir", set_config_cgroup_dir, get_config_cgroup_dir, clr_config_cgroup_dir, },
{ "lxc.cgroup.relative", set_config_cgroup_relative, get_config_cgroup_relative, clr_config_cgroup_relative, }, { "lxc.cgroup.relative", set_config_cgroup_relative, get_config_cgroup_relative, clr_config_cgroup_relative, },
...@@ -221,6 +223,8 @@ static struct lxc_config_t config_jump_table[] = { ...@@ -221,6 +223,8 @@ static struct lxc_config_t config_jump_table[] = {
{ "lxc.namespace.clone", set_config_namespace_clone, get_config_namespace_clone, clr_config_namespace_clone, }, { "lxc.namespace.clone", set_config_namespace_clone, get_config_namespace_clone, clr_config_namespace_clone, },
{ "lxc.namespace.keep", set_config_namespace_keep, get_config_namespace_keep, clr_config_namespace_keep, }, { "lxc.namespace.keep", set_config_namespace_keep, get_config_namespace_keep, clr_config_namespace_keep, },
{ "lxc.namespace.share", set_config_namespace_share, get_config_namespace_share, clr_config_namespace_share, }, { "lxc.namespace.share", set_config_namespace_share, get_config_namespace_share, clr_config_namespace_share, },
{ "lxc.time.offset.boot", set_config_time_offset_boot, get_config_time_offset_boot, clr_config_time_offset_boot, },
{ "lxc.time.offset.monotonic", set_config_time_offset_monotonic, get_config_time_offset_monotonic, clr_config_time_offset_monotonic, },
{ "lxc.net.flags", set_config_net_flags, get_config_net_flags, clr_config_net_flags, }, { "lxc.net.flags", set_config_net_flags, get_config_net_flags, clr_config_net_flags, },
{ "lxc.net.hwaddr", set_config_net_hwaddr, get_config_net_hwaddr, clr_config_net_hwaddr, }, { "lxc.net.hwaddr", set_config_net_hwaddr, get_config_net_hwaddr, clr_config_net_hwaddr, },
{ "lxc.net.ipv4.address", set_config_net_ipv4_address, get_config_net_ipv4_address, clr_config_net_ipv4_address, }, { "lxc.net.ipv4.address", set_config_net_ipv4_address, get_config_net_ipv4_address, clr_config_net_ipv4_address, },
...@@ -2812,6 +2816,76 @@ static int set_config_namespace_keep(const char *key, const char *value, ...@@ -2812,6 +2816,76 @@ static int set_config_namespace_keep(const char *key, const char *value,
return 0; return 0;
} }
static int set_config_time_offset_boot(const char *key, const char *value,
struct lxc_conf *lxc_conf, void *data)
{
int ret;
char *unit;
int64_t offset = 0;
char buf[STRLITERALLEN("ms") + 1];
if (lxc_config_value_empty(value))
return clr_config_time_offset_boot(key, lxc_conf, data);
ret = lxc_safe_int64_residual(value, &offset, 10, buf, sizeof(buf));
if (ret)
return ret;
/* TODO: Handle overflow. */
unit = lxc_trim_whitespace_in_place(buf);
if (strcmp(unit, "h") == 0)
lxc_conf->timens.s_boot = offset * 3600;
else if (strcmp(unit, "m") == 0)
lxc_conf->timens.s_boot = offset * 60;
else if (strcmp(unit, "s") == 0)
lxc_conf->timens.s_boot = offset;
else if (strcmp(unit, "ms") == 0)
lxc_conf->timens.ns_boot = offset * 1000000;
else if (strcmp(unit, "us") == 0)
lxc_conf->timens.ns_boot = offset * 1000;
else if (strcmp(unit, "ns") == 0)
lxc_conf->timens.ns_boot = offset;
else
return ret_errno(EINVAL);
return 0;
}
static int set_config_time_offset_monotonic(const char *key, const char *value,
struct lxc_conf *lxc_conf, void *data)
{
int ret;
char *unit;
int64_t offset = 0;
char buf[STRLITERALLEN("ms") + 1];
if (lxc_config_value_empty(value))
return clr_config_time_offset_monotonic(key, lxc_conf, data);
ret = lxc_safe_int64_residual(value, &offset, 10, buf, sizeof(buf));
if (ret)
return ret;
// TODO: Handle overflow.
unit = lxc_trim_whitespace_in_place(buf);
if (strcmp(unit, "h") == 0)
lxc_conf->timens.s_monotonic = offset * 3600;
else if (strcmp(unit, "m") == 0)
lxc_conf->timens.s_monotonic = offset * 60;
else if (strcmp(unit, "s") == 0)
lxc_conf->timens.s_monotonic = offset;
else if (strcmp(unit, "ms") == 0)
lxc_conf->timens.ns_monotonic = offset * 1000000;
else if (strcmp(unit, "us") == 0)
lxc_conf->timens.ns_monotonic = offset * 1000;
else if (strcmp(unit, "ns") == 0)
lxc_conf->timens.ns_monotonic = offset;
else
return ret_errno(EINVAL);
return 0;
}
static int set_config_namespace_share(const char *key, const char *value, static int set_config_namespace_share(const char *key, const char *value,
struct lxc_conf *lxc_conf, void *data) struct lxc_conf *lxc_conf, void *data)
{ {
...@@ -4497,6 +4571,46 @@ static int get_config_namespace_keep(const char *key, char *retv, int inlen, ...@@ -4497,6 +4571,46 @@ static int get_config_namespace_keep(const char *key, char *retv, int inlen,
return fulllen; return fulllen;
} }
static int get_config_time_offset_boot(const char *key, char *retv, int inlen, struct lxc_conf *c,
void *data)
{
int len;
int fulllen = 0;
if (!retv)
inlen = 0;
else
memset(retv, 0, inlen);
if (c->timens.s_boot) {
strprint(retv, inlen, "%" PRId64 " s\n", c->timens.s_boot);
} else {
strprint(retv, inlen, "%" PRId64 " ns\n", c->timens.ns_boot);
}
return fulllen;
}
static int get_config_time_offset_monotonic(const char *key, char *retv, int inlen,
struct lxc_conf *c, void *data)
{
int len;
int fulllen = 0;
if (!retv)
inlen = 0;
else
memset(retv, 0, inlen);
if (c->timens.s_monotonic) {
strprint(retv, inlen, "%" PRId64 "s\n", c->timens.s_monotonic);
} else {
strprint(retv, inlen, "%" PRId64 "ns\n", c->timens.ns_monotonic);
}
return fulllen;
}
static int get_config_namespace_share(const char *key, char *retv, int inlen, static int get_config_namespace_share(const char *key, char *retv, int inlen,
struct lxc_conf *c, void *data) struct lxc_conf *c, void *data)
{ {
...@@ -5030,6 +5144,20 @@ static int clr_config_namespace_keep(const char *key, struct lxc_conf *lxc_conf, ...@@ -5030,6 +5144,20 @@ static int clr_config_namespace_keep(const char *key, struct lxc_conf *lxc_conf,
return 0; return 0;
} }
static int clr_config_time_offset_boot(const char *key, struct lxc_conf *lxc_conf, void *data)
{
lxc_conf->timens.s_boot = 0;
lxc_conf->timens.ns_boot = 0;
return 0;
}
static int clr_config_time_offset_monotonic(const char *key, struct lxc_conf *lxc_conf, void *data)
{
lxc_conf->timens.s_monotonic = 0;
lxc_conf->timens.ns_monotonic = 0;
return 0;
}
static int clr_config_namespace_share(const char *key, static int clr_config_namespace_share(const char *key,
struct lxc_conf *lxc_conf, void *data) struct lxc_conf *lxc_conf, void *data)
{ {
......
...@@ -512,3 +512,30 @@ FILE *fdopen_cached(int fd, const char *mode, void **caller_freed_buffer) ...@@ -512,3 +512,30 @@ FILE *fdopen_cached(int fd, const char *mode, void **caller_freed_buffer)
#endif #endif
return f; return f;
} }
int timens_offset_write(clockid_t clk_id, int64_t s_offset, int64_t ns_offset)
{
__do_close int fd = -EBADF;
int ret;
ssize_t len;
char buf[INTTYPE_TO_STRLEN(int) +
STRLITERALLEN(" ") + INTTYPE_TO_STRLEN(int64_t) +
STRLITERALLEN(" ") + INTTYPE_TO_STRLEN(int64_t) + 1];
if (clk_id == CLOCK_MONOTONIC_COARSE || clk_id == CLOCK_MONOTONIC_RAW)
clk_id = CLOCK_MONOTONIC;
fd = open("/proc/self/timens_offsets", O_WRONLY | O_CLOEXEC);
if (fd < 0)
return -errno;
len = snprintf(buf, sizeof(buf), "%d %" PRId64 " %" PRId64, clk_id, s_offset, ns_offset);
if (len < 0 || len >= sizeof(buf))
return ret_errno(EFBIG);
ret = lxc_write_nointr(fd, buf, len);
if (ret < 0 || (size_t)ret != len)
return -EIO;
return 0;
}
...@@ -82,5 +82,6 @@ extern int lxc_open_dirfd(const char *dir); ...@@ -82,5 +82,6 @@ extern int lxc_open_dirfd(const char *dir);
extern FILE *fdopen_cached(int fd, const char *mode, void **caller_freed_buffer); extern FILE *fdopen_cached(int fd, const char *mode, void **caller_freed_buffer);
extern FILE *fopen_cached(const char *path, const char *mode, extern FILE *fopen_cached(const char *path, const char *mode,
void **caller_freed_buffer); void **caller_freed_buffer);
extern int timens_offset_write(clockid_t clk_id, int64_t s_offset, int64_t ns_offset);
#endif /* __LXC_FILE_UTILS_H */ #endif /* __LXC_FILE_UTILS_H */
...@@ -44,7 +44,8 @@ const struct ns_info ns_info[LXC_NS_MAX] = { ...@@ -44,7 +44,8 @@ const struct ns_info ns_info[LXC_NS_MAX] = {
[LXC_NS_UTS] = { "uts", CLONE_NEWUTS, "CLONE_NEWUTS", "LXC_UTS_NS" }, [LXC_NS_UTS] = { "uts", CLONE_NEWUTS, "CLONE_NEWUTS", "LXC_UTS_NS" },
[LXC_NS_IPC] = { "ipc", CLONE_NEWIPC, "CLONE_NEWIPC", "LXC_IPC_NS" }, [LXC_NS_IPC] = { "ipc", CLONE_NEWIPC, "CLONE_NEWIPC", "LXC_IPC_NS" },
[LXC_NS_NET] = { "net", CLONE_NEWNET, "CLONE_NEWNET", "LXC_NET_NS" }, [LXC_NS_NET] = { "net", CLONE_NEWNET, "CLONE_NEWNET", "LXC_NET_NS" },
[LXC_NS_CGROUP] = { "cgroup", CLONE_NEWCGROUP, "CLONE_NEWCGROUP", "LXC_CGROUP_NS" } [LXC_NS_CGROUP] = { "cgroup", CLONE_NEWCGROUP, "CLONE_NEWCGROUP", "LXC_CGROUP_NS" },
[LXC_NS_TIME] = { "time", CLONE_NEWTIME, "CLONE_NEWTIME", "LXC_TIME_NS" },
}; };
int lxc_namespace_2_cloneflag(const char *namespace) int lxc_namespace_2_cloneflag(const char *namespace)
......
...@@ -15,6 +15,7 @@ enum { ...@@ -15,6 +15,7 @@ enum {
LXC_NS_IPC, LXC_NS_IPC,
LXC_NS_NET, LXC_NS_NET,
LXC_NS_CGROUP, LXC_NS_CGROUP,
LXC_NS_TIME,
LXC_NS_MAX LXC_NS_MAX
}; };
......
...@@ -1205,6 +1205,55 @@ static int do_start(void *data) ...@@ -1205,6 +1205,55 @@ static int do_start(void *data)
} }
} }
if (handler->ns_clone_flags & CLONE_NEWTIME) {
ret = unshare(CLONE_NEWTIME);
if (ret < 0) {
if (errno != EINVAL) {
SYSERROR("Failed to unshare CLONE_NEWTIME");
goto out_warn_father;
}
handler->ns_clone_flags &= ~CLONE_NEWTIME;
SYSINFO("Kernel does not support CLONE_NEWTIME");
} else {
__do_close int timens_fd = -EBADF;
INFO("Unshared CLONE_NEWTIME");
if (handler->conf->timens.s_boot)
ret = timens_offset_write(CLOCK_BOOTTIME, handler->conf->timens.s_boot, 0);
else if (handler->conf->timens.ns_boot)
ret = timens_offset_write(CLOCK_BOOTTIME, 0, handler->conf->timens.ns_boot);
if (ret) {
SYSERROR("Failed to write CLONE_BOOTTIME offset");
goto out_warn_father;
}
TRACE("Wrote CLOCK_BOOTTIME offset");
if (handler->conf->timens.s_monotonic)
ret = timens_offset_write(CLOCK_MONOTONIC, handler->conf->timens.s_monotonic, 0);
else if (handler->conf->timens.ns_monotonic)
ret = timens_offset_write(CLOCK_MONOTONIC, 0, handler->conf->timens.ns_monotonic);
if (ret) {
SYSERROR("Failed to write CLONE_MONOTONIC offset");
goto out_warn_father;
}
TRACE("Wrote CLOCK_MONOTONIC offset");
timens_fd = open("/proc/self/ns/time_for_children", O_RDONLY | O_CLOEXEC);
if (timens_fd < 0) {
SYSERROR("Failed to open \"/proc/self/ns/time_for_children\"");
goto out_warn_father;
}
ret = setns(timens_fd, CLONE_NEWTIME);
if (ret) {
SYSERROR("Failed to setns(%d(\"/proc/self/ns/time_for_children\"))", timens_fd);
goto out_warn_father;
}
}
}
/* Add the requested environment variables to the current environment to /* Add the requested environment variables to the current environment to
* allow them to be used by the various hooks, such as the start hook * allow them to be used by the various hooks, such as the start hook
* below. * below.
...@@ -1452,6 +1501,8 @@ int resolve_clone_flags(struct lxc_handler *handler) ...@@ -1452,6 +1501,8 @@ int resolve_clone_flags(struct lxc_handler *handler)
{ {
int i; int i;
struct lxc_conf *conf = handler->conf; struct lxc_conf *conf = handler->conf;
bool wants_timens = conf->timens.s_boot || conf->timens.ns_boot ||
conf->timens.s_monotonic || conf->timens.ns_monotonic;
for (i = 0; i < LXC_NS_MAX; i++) { for (i = 0; i < LXC_NS_MAX; i++) {
if (conf->ns_keep) { if (conf->ns_keep) {
...@@ -1470,6 +1521,9 @@ int resolve_clone_flags(struct lxc_handler *handler) ...@@ -1470,6 +1521,9 @@ int resolve_clone_flags(struct lxc_handler *handler)
if (i == LXC_NS_CGROUP && !cgns_supported()) if (i == LXC_NS_CGROUP && !cgns_supported())
continue; continue;
if (i == LXC_NS_TIME && !wants_timens)
continue;
handler->ns_clone_flags |= ns_info[i].clone_flag; handler->ns_clone_flags |= ns_info[i].clone_flag;
} }
...@@ -1480,6 +1534,9 @@ int resolve_clone_flags(struct lxc_handler *handler) ...@@ -1480,6 +1534,9 @@ int resolve_clone_flags(struct lxc_handler *handler)
TRACE("Sharing %s namespace", ns_info[i].proc_name); TRACE("Sharing %s namespace", ns_info[i].proc_name);
} }
if (wants_timens && (conf->ns_keep & ns_info[LXC_NS_TIME].clone_flag))
return log_trace_errno(-1, EINVAL, "Requested to keep time namespace while also specifying offsets");
return 0; return 0;
} }
...@@ -1614,6 +1671,9 @@ static int lxc_spawn(struct lxc_handler *handler) ...@@ -1614,6 +1671,9 @@ static int lxc_spawn(struct lxc_handler *handler)
/* The cgroup namespace gets unshare()ed not clone()ed. */ /* The cgroup namespace gets unshare()ed not clone()ed. */
handler->ns_on_clone_flags &= ~CLONE_NEWCGROUP; handler->ns_on_clone_flags &= ~CLONE_NEWCGROUP;
/* The time namespace (currently) gets unshare()ed not clone()ed. */
handler->ns_on_clone_flags &= ~CLONE_NEWTIME;
if (share_ns) { if (share_ns) {
pid_t attacher_pid; pid_t attacher_pid;
......
...@@ -667,6 +667,51 @@ int lxc_safe_uint64(const char *numstr, uint64_t *converted, int base) ...@@ -667,6 +667,51 @@ int lxc_safe_uint64(const char *numstr, uint64_t *converted, int base)
return 0; return 0;
} }
int lxc_safe_int64_residual(const char *numstr, int64_t *converted, int base, char *residual,
size_t residual_len)
{
char *remaining = NULL;
int64_t u;
if (residual && residual_len == 0)
return ret_errno(EINVAL);
if (!residual && residual_len != 0)
return ret_errno(EINVAL);
while (isspace(*numstr))
numstr++;
errno = 0;
u = strtoll(numstr, &remaining, base);
if (errno == ERANGE && u == INT64_MAX)
return -ERANGE;
if (remaining == numstr)
return -EINVAL;
if (residual) {
size_t len = 0;
if (*remaining == '\0') {
memset(residual, 0, residual_len);
goto out;
}
len = strlen(remaining);
if (len >= residual_len)
return -EINVAL;
memcpy(residual, remaining, len);
} else if (*remaining != '\0') {
return -EINVAL;
}
out:
*converted = u;
return 0;
}
int lxc_safe_int(const char *numstr, int *converted) int lxc_safe_int(const char *numstr, int *converted)
{ {
char *err = NULL; char *err = NULL;
......
...@@ -76,6 +76,8 @@ extern int lxc_safe_long(const char *numstr, long int *converted); ...@@ -76,6 +76,8 @@ extern int lxc_safe_long(const char *numstr, long int *converted);
extern int lxc_safe_long_long(const char *numstr, long long int *converted); extern int lxc_safe_long_long(const char *numstr, long long int *converted);
extern int lxc_safe_ulong(const char *numstr, unsigned long *converted); extern int lxc_safe_ulong(const char *numstr, unsigned long *converted);
extern int lxc_safe_uint64(const char *numstr, uint64_t *converted, int base); extern int lxc_safe_uint64(const char *numstr, uint64_t *converted, int base);
extern int lxc_safe_int64_residual(const char *numstr, int64_t *converted, int base, char *residual,
size_t residual_len);
/* Handles B, kb, MB, GB. Detects overflows and reports -ERANGE. */ /* Handles B, kb, MB, GB. Detects overflows and reports -ERANGE. */
extern int parse_byte_size_string(const char *s, int64_t *converted); extern int parse_byte_size_string(const char *s, int64_t *converted);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment