diff --git a/.gitignore b/.gitignore index 4f60f4330..46c999e03 100644 --- a/.gitignore +++ b/.gitignore @@ -12,9 +12,12 @@ libnvidia-container.so* deps src/build.h -src/driver_clt.c -src/driver_rpc.h -src/driver_svc.c -src/driver_xdr.c +src/nvc.h +src/nvc_clt.c +src/nvc_rpc.h +src/nvc_svc.c +src/nvc_xdr.c +src/nvcgo/libnvidia-container-go.h + /dist -/.vscode \ No newline at end of file +/.vscode diff --git a/mk/nvidia-modprobe.mk b/mk/nvidia-modprobe.mk index 340622246..1633e0c47 100644 --- a/mk/nvidia-modprobe.mk +++ b/mk/nvidia-modprobe.mk @@ -6,7 +6,7 @@ include $(MAKE_DIR)/common.mk ##### Source definitions ##### -VERSION := 495.44 +VERSION := 550.54.14 PREFIX := nvidia-modprobe-$(VERSION) URL := https://github.com/NVIDIA/nvidia-modprobe/archive/$(VERSION).tar.gz diff --git a/mk/nvidia-modprobe.patch b/mk/nvidia-modprobe.patch index d99a17488..70d97d539 100644 --- a/mk/nvidia-modprobe.patch +++ b/mk/nvidia-modprobe.patch @@ -1,7 +1,7 @@ diff -ruN nvidia-modprobe-495.44/modprobe-utils/nvidia-modprobe-utils.c nvidia-modprobe-495.44-patched/modprobe-utils/nvidia-modprobe-utils.c --- nvidia-modprobe-495.44/modprobe-utils/nvidia-modprobe-utils.c 2021-11-13 14:36:58.096684602 +0000 +++ nvidia-modprobe-495.44-patched/modprobe-utils/nvidia-modprobe-utils.c 2021-11-13 14:43:40.965146390 +0000 -@@ -888,10 +888,10 @@ +@@ -959,10 +959,10 @@ return mknod_helper(major, minor_num, vgpu_dev_name, NV_PROC_REGISTRY_PATH); } @@ -19,11 +19,13 @@ diff -ruN nvidia-modprobe-495.44/modprobe-utils/nvidia-modprobe-utils.c nvidia-m diff -ruN nvidia-modprobe-495.44/modprobe-utils/nvidia-modprobe-utils.h nvidia-modprobe-495.44-patched/modprobe-utils/nvidia-modprobe-utils.h --- nvidia-modprobe-495.44/modprobe-utils/nvidia-modprobe-utils.h 2021-11-13 14:36:58.096684602 +0000 +++ nvidia-modprobe-495.44-patched/modprobe-utils/nvidia-modprobe-utils.h 2021-11-13 14:38:34.078700961 +0000 -@@ -81,6 +81,7 @@ +@@ -87,6 +87,7 @@ int nvidia_nvswitch_get_file_state(int minor); int nvidia_cap_mknod(const char* cap_file_path, int *minor); int nvidia_cap_get_file_state(const char* cap_file_path); +int nvidia_cap_get_device_file_attrs(const char* cap_file_path, int *major, int *minor, char *name); + int nvidia_cap_imex_channel_mknod(int minor); + int nvidia_cap_imex_channel_file_state(int minor); int nvidia_get_chardev_major(const char *name); int nvidia_msr_modprobe(void); diff --git a/pkg/deb/libnvidia-container@MAJOR@.symbols b/pkg/deb/libnvidia-container@MAJOR@.symbols index f861a29b1..b340eae5b 100644 --- a/pkg/deb/libnvidia-container@MAJOR@.symbols +++ b/pkg/deb/libnvidia-container@MAJOR@.symbols @@ -17,6 +17,7 @@ libnvidia-container.so.1 libnvidia-container1 #MINVER# nvc_mig_config_global_caps_mount@NVC_1.0 @VERSION_TAG@ nvc_mig_monitor_global_caps_mount@NVC_1.0 @VERSION_TAG@ nvc_device_mig_caps_mount@NVC_1.0 @VERSION_TAG@ + nvc_imex_channel_mount@NVC_1.0 @VERSION_TAG@ nvc_driver_info_free@NVC_1.0 @VERSION_TAG@ nvc_driver_info_new@NVC_1.0 @VERSION_TAG@ nvc_driver_mount@NVC_1.0 @VERSION_TAG@ diff --git a/src/cli/cli.h b/src/cli/cli.h index 61e8b5ae3..f9c35f10c 100644 --- a/src/cli/cli.h +++ b/src/cli/cli.h @@ -54,6 +54,7 @@ struct context { char *devices; char *mig_config; char *mig_monitor; + char *imex_channels; }; bool matches_pci_format(const char *gpu, char *buf, size_t bufsize); @@ -74,6 +75,11 @@ void free_devices(struct devices *d); int print_nvcaps_device_from_proc_file(struct nvc_context *, const char*, const char*); int print_all_mig_minor_devices(const struct nvc_device_node *); +int parse_imex_info( + struct error *err, + char *chans, + struct nvc_imex_info *imex); + int select_devices( struct error *err, char *devs, diff --git a/src/cli/common.c b/src/cli/common.c index f60504a4a..25aa64606 100644 --- a/src/cli/common.c +++ b/src/cli/common.c @@ -442,6 +442,59 @@ select_mig_monitor_devices( return (-1); } +int +parse_imex_info( + struct error *err, + char *chans, + struct nvc_imex_info *imex) +{ + // Initialize local variables. + char sep[2] = ","; + struct error ierr = {0}; + char *chan = NULL; + char *ptr; + size_t id; + size_t max_chans = str_count_tokens(chans, ','); + + /* Clear the imex struct */ + memset(imex, 0, sizeof(*imex)); + + /* Short circuit if max_chans == 0 */ + if (max_chans == 0) + return (0); + + /* Allocate space for all IMEX channels */ + if ((imex->chans = xcalloc(err, max_chans, sizeof(*imex->chans))) == NULL) + return (-1); + + // Walk through the comma separated chans string and populate + // 'imex->chans' from it. + while ((chan = strsep(&chans, sep)) != NULL) { + // Allow extra commas between device strings. + if (*chan == '\0') + continue; + + // Get the IMEX channel ID. + // Channel IDs must fit in the minor number of a dev_t (so within 20 bits). + id = strtoumax(chan, &ptr, 10); + if (!(*ptr == '\0' && id < (1 << 20))) { + error_setx(&ierr, "unsupported IMEX channel value: %s", chan); + goto fail; + } + + // Add the IMEX channel to the selected list. + imex->chans[imex->nchans].id = (int)id; + imex->nchans++; + } + + return (0); + + fail: + error_setx(err, "%s", ierr.msg); + error_reset(&ierr); + return (-1); +} + int new_devices(struct error *err, const struct nvc_device_info *dev, struct devices *d) { diff --git a/src/cli/configure.c b/src/cli/configure.c index 415f6b385..8890b4eae 100644 --- a/src/cli/configure.c +++ b/src/cli/configure.c @@ -30,8 +30,9 @@ const struct argp configure_usage = { {"compat32", 0x80, NULL, 0, "Enable 32bits compatibility", -1}, {"mig-config", 0x81, "ID", 0, "Enable configuration of MIG devices", -1}, {"mig-monitor", 0x82, "ID", 0, "Enable monitoring of MIG devices", -1}, - {"no-cgroups", 0x83, NULL, 0, "Don't use cgroup enforcement", -1}, - {"no-devbind", 0x84, NULL, 0, "Don't bind mount devices", -1}, + {"imex-channel", 0x83, "CHANNEL", 0, "IMEX channel ID(s) to inject", -1}, + {"no-cgroups", 0x84, NULL, 0, "Don't use cgroup enforcement", -1}, + {"no-devbind", 0x85, NULL, 0, "Don't bind mount devices", -1}, {0}, }, configure_parser, @@ -138,10 +139,14 @@ configure_parser(int key, char *arg, struct argp_state *state) goto fatal; break; case 0x83: - if (str_join(&err, &ctx->container_flags, "no-cgroups", " ") < 0) + if (str_join(&err, &ctx->imex_channels, arg, ",") < 0) goto fatal; break; case 0x84: + if (str_join(&err, &ctx->container_flags, "no-cgroups", " ") < 0) + goto fatal; + break; + case 0x85: if (str_join(&err, &ctx->container_flags, "no-devbind", " ") < 0) goto fatal; break; @@ -262,6 +267,10 @@ configure_command(const struct context *ctx) nvc_cfg->gid = ctx->gid; nvc_cfg->root = ctx->root; nvc_cfg->ldcache = ctx->ldcache; + if (parse_imex_info(&err, ctx->imex_channels, &nvc_cfg->imex) < 0) { + warnx("error parsing IMEX info: %s", err.msg); + goto fail; + } if (libnvc.init(nvc, nvc_cfg, ctx->init_flags) < 0) { warnx("initialization error: %s", libnvc.error(nvc)); goto fail; @@ -319,7 +328,7 @@ configure_command(const struct context *ctx) goto fail; } - /* Select the devices available for MIG monitor among the visible . */ + /* Select the devices available for MIG monitor among the visible devices. */ if (select_mig_monitor_devices(&err, ctx->mig_monitor, &devices, &mig_monitor_devices) < 0) { warnx("mig-monitor error: %s", err.msg); goto fail; @@ -359,7 +368,7 @@ configure_command(const struct context *ctx) } } - /* Mount the driver, visible devices, mig-configs and mig-monitors. */ + /* Mount the driver, visible devices, mig-configs, mig-monitors, and imex-channels. */ if (perm_set_capabilities(&err, CAP_EFFECTIVE, ecaps[NVC_MOUNT], ecaps_size(NVC_MOUNT)) < 0) { warnx("permission error: %s", err.msg); goto fail; @@ -406,6 +415,12 @@ configure_command(const struct context *ctx) } } } + for (size_t i = 0; i < nvc_cfg->imex.nchans; ++i) { + if (libnvc.imex_channel_mount(nvc, cnt, &nvc_cfg->imex.chans[i]) < 0) { + warnx("mount error: %s", libnvc.error(nvc)); + goto fail; + } + } /* Update the container ldcache. */ if (perm_set_capabilities(&err, CAP_EFFECTIVE, ecaps[NVC_LDCACHE], ecaps_size(NVC_LDCACHE)) < 0) { @@ -424,6 +439,7 @@ configure_command(const struct context *ctx) rv = EXIT_SUCCESS; fail: + free(nvc_cfg->imex.chans); free_devices(&devices); libnvc.shutdown(nvc); libnvc.container_free(cnt); diff --git a/src/cli/libnvc.c b/src/cli/libnvc.c index 30f13db18..64a138b8a 100644 --- a/src/cli/libnvc.c +++ b/src/cli/libnvc.c @@ -161,6 +161,7 @@ load_libnvc_v1(void) load_libnvc_func(mig_config_global_caps_mount); load_libnvc_func(mig_monitor_global_caps_mount); load_libnvc_func(device_mig_caps_mount); + load_libnvc_func(imex_channel_mount); return (0); } diff --git a/src/cli/libnvc.h b/src/cli/libnvc.h index 03cc2c227..0966c0fb5 100644 --- a/src/cli/libnvc.h +++ b/src/cli/libnvc.h @@ -50,6 +50,7 @@ struct libnvc { libnvc_entry(mig_config_global_caps_mount); libnvc_entry(mig_monitor_global_caps_mount); libnvc_entry(device_mig_caps_mount); + libnvc_entry(imex_channel_mount); }; int load_libnvc(void); diff --git a/src/cli/list.c b/src/cli/list.c index a05be1c2d..8574a0c94 100644 --- a/src/cli/list.c +++ b/src/cli/list.c @@ -21,6 +21,7 @@ const struct argp list_usage = { {"compat32", 0x80, NULL, 0, "Enable 32bits compatibility", -1}, {"mig-config", 0x81, "ID", 0, "MIG devices to list config capabilities files for", -1}, {"mig-monitor", 0x82, "ID", 0, "MIG devices to list monitor capabilities files for", -1}, + {"imex-channel", 0x83, "CHANNEL", 0, "IMEX channel ID(s) to inject", -1}, {0}, }, list_parser, @@ -65,8 +66,12 @@ list_parser(int key, char *arg, struct argp_state *state) if (str_join(&err, &ctx->mig_monitor, arg, ",") < 0) goto fatal; break; + case 0x83: + if (str_join(&err, &ctx->imex_channels, arg, ",") < 0) + goto fatal; + break; case ARGP_KEY_END: - if (state->argc == 1) { + if (state->argc == 1 || (state->argc == 2 && ctx->imex_channels != NULL)) { if ((ctx->devices = xstrdup(&err, "all")) == NULL) goto fatal; ctx->mig_config = NULL; @@ -127,6 +132,10 @@ list_command(const struct context *ctx) nvc_cfg->gid = (!run_as_root && ctx->gid == (gid_t)-1) ? getegid() : ctx->gid; nvc_cfg->root = ctx->root; nvc_cfg->ldcache = ctx->ldcache; + if (parse_imex_info(&err, ctx->imex_channels, &nvc_cfg->imex) < 0) { + warnx("error parsing IMEX info: %s", err.msg); + goto fail; + } if (libnvc.init(nvc, nvc_cfg, ctx->init_flags) < 0) { warnx("initialization error: %s", libnvc.error(nvc)); goto fail; @@ -203,6 +212,13 @@ list_command(const struct context *ctx) } } + /* List the IMEX channel devices. */ + if (ctx->imex_channels != NULL) { + for (size_t i = 0; i < nvc_cfg->imex.nchans; ++i) { + printf(NV_CAPS_IMEX_DEVICE_PATH"\n", nvc_cfg->imex.chans[i].id); + } + } + /* List the files required for MIG configuration of the visible devices */ if (mig_config_devices.all && mig_config_devices.ngpus) { printf("%s/%s\n", NV_MIG_CAPS_PATH, NV_MIG_CONFIG_FILE); @@ -258,6 +274,7 @@ list_command(const struct context *ctx) } rv = EXIT_SUCCESS; fail: + free(nvc_cfg->imex.chans); free_devices(&devices); libnvc.shutdown(nvc); libnvc.device_info_free(dev); diff --git a/src/cli/main.c b/src/cli/main.c index 04523e1e1..5399b34e8 100644 --- a/src/cli/main.c +++ b/src/cli/main.c @@ -146,5 +146,8 @@ main(int argc, char *argv[]) free(ctx.devices); free(ctx.init_flags); free(ctx.container_flags); + free(ctx.mig_config); + free(ctx.mig_monitor); + free(ctx.imex_channels); return (rv); } diff --git a/src/libnvidia-container.lds b/src/libnvidia-container.lds index 038c96d18..440665998 100644 --- a/src/libnvidia-container.lds +++ b/src/libnvidia-container.lds @@ -39,6 +39,7 @@ VERSION { nvc_mig_config_global_caps_mount; nvc_mig_monitor_global_caps_mount; nvc_device_mig_caps_mount; + nvc_imex_channel_mount; __ubsan_default_options; local: diff --git a/src/nvc.c b/src/nvc.c index b220db81b..9a845cea1 100644 --- a/src/nvc.c +++ b/src/nvc.c @@ -33,7 +33,7 @@ #include "xfuncs.h" static int init_within_userns(struct error *); -static int load_kernel_modules(struct error *, const char *); +static int load_kernel_modules(struct error *, const char *, const struct nvc_imex_info *); static int copy_config(struct error *, struct nvc_context *, const struct nvc_config *); const char interpreter[] __attribute__((section(".interp"))) = LIB_DIR "/" LD_SO; @@ -229,7 +229,7 @@ mig_nvcaps_mknodes(struct error *err, int num_gpus) { } static int -load_kernel_modules(struct error *err, const char *root) +load_kernel_modules(struct error *err, const char *root, const struct nvc_imex_info *imex) { int userns; pid_t pid; @@ -290,6 +290,11 @@ load_kernel_modules(struct error *err, const char *root) log_info("running mknod for all nvcaps in " NV_CAPS_DEVICE_DIR); if (mig_nvcaps_mknodes(err, devs.num_matches) < 0) log_errf("could not create kernel module device nodes: %s", err->msg); + for (int i = 0; i < (int)imex->nchans; ++i) { + log_infof("running mknod for " NV_CAPS_IMEX_DEVICE_PATH, imex->chans[i].id); + if (nvidia_cap_imex_channel_mknod(imex->chans[i].id) == 0) + log_errf("could not mknod for IMEX channel %d", imex->chans[i].id); + } error_reset(err); } @@ -347,9 +352,21 @@ copy_config(struct error *err, struct nvc_context *ctx, const struct nvc_config ctx->cfg.gid = (gid_t)gid; } + if (cfg->imex.nchans > 0) { + if ((ctx->cfg.imex.chans = xcalloc(err, cfg->imex.nchans, sizeof(*ctx->cfg.imex.chans))) == NULL) + return (-1); + } + for (size_t i = 0; i < cfg->imex.nchans; ++i) { + ctx->cfg.imex.chans[i] = cfg->imex.chans[i]; + } + ctx->cfg.imex.nchans = cfg->imex.nchans; + log_infof("using root %s", ctx->cfg.root); log_infof("using ldcache %s", ctx->cfg.ldcache); log_infof("using unprivileged user %"PRIu32":%"PRIu32, (uint32_t)ctx->cfg.uid, (uint32_t)ctx->cfg.gid); + for (size_t i = 0; i < ctx->cfg.imex.nchans; ++i) { + log_infof("using IMEX channel %d", ctx->cfg.imex.chans[i].id); + } return (0); } @@ -364,7 +381,7 @@ nvc_init(struct nvc_context *ctx, const struct nvc_config *cfg, const char *opts if (ctx->initialized) return (0); if (cfg == NULL) - cfg = &(struct nvc_config){NULL, NULL, (uid_t)-1, (gid_t)-1}; + cfg = &(struct nvc_config){NULL, NULL, (uid_t)-1, (gid_t)-1, {0}}; if (validate_args(ctx, !str_empty(cfg->ldcache) && !str_empty(cfg->root)) < 0) return (-1); if (opts == NULL) @@ -403,7 +420,7 @@ nvc_init(struct nvc_context *ctx, const struct nvc_config *cfg, const char *opts if (flags & OPT_LOAD_KMODS) { if (ctx->dxcore.initialized) log_warn("skipping kernel modules load on WSL"); - else if (load_kernel_modules(&ctx->err, ctx->cfg.root) < 0) + else if (load_kernel_modules(&ctx->err, ctx->cfg.root, &ctx->cfg.imex) < 0) goto fail; } @@ -421,6 +438,7 @@ nvc_init(struct nvc_context *ctx, const struct nvc_config *cfg, const char *opts fail: free(ctx->cfg.root); free(ctx->cfg.ldcache); + free(ctx->cfg.imex.chans); xclose(ctx->mnt_ns); return (-1); } @@ -453,6 +471,7 @@ nvc_shutdown(struct nvc_context *ctx) free(ctx->cfg.root); free(ctx->cfg.ldcache); + free(ctx->cfg.imex.chans); xclose(ctx->mnt_ns); memset(&ctx->cfg, 0, sizeof(ctx->cfg)); diff --git a/src/nvc.h.template b/src/nvc.h.template index 462a36ada..04d4afc84 100644 --- a/src/nvc.h.template +++ b/src/nvc.h.template @@ -45,6 +45,15 @@ extern "C" { struct nvc_context; struct nvc_container; +struct nvc_imex_channel { + int id; +}; + +struct nvc_imex_info { + struct nvc_imex_channel *chans; + size_t nchans; +}; + struct nvc_version { unsigned int major; unsigned int minor; @@ -57,6 +66,7 @@ struct nvc_config { char *ldcache; uid_t uid; gid_t gid; + struct nvc_imex_info imex; }; struct nvc_device_node { @@ -161,6 +171,8 @@ int nvc_mig_monitor_global_caps_mount(struct nvc_context *, const struct nvc_con int nvc_device_mig_caps_mount(struct nvc_context *, const struct nvc_container *, const struct nvc_device *); +int nvc_imex_channel_mount(struct nvc_context *, const struct nvc_container *, const struct nvc_imex_channel *); + int nvc_ldcache_update(struct nvc_context *, const struct nvc_container *); const char *nvc_error(struct nvc_context *); diff --git a/src/nvc_info.c b/src/nvc_info.c index 2d203ae96..952850626 100644 --- a/src/nvc_info.c +++ b/src/nvc_info.c @@ -37,7 +37,6 @@ static int select_libraries(struct error *, void *, const char *, const char *, static int select_wsl_libraries(struct error *, void *, const char *, const char *, const char *); static int find_library_paths(struct error *, struct dxcore_context *, struct nvc_driver_info *, const char *, const char *, const char * const [], size_t); static int find_binary_paths(struct error *, struct dxcore_context*, struct nvc_driver_info *, const char *, const char * const [], size_t); -static int find_device_node(struct error *, const char *, const char *, struct nvc_device_node *); static int find_path(struct error *, const char *, const char *, const char *, char **); static int lookup_paths(struct error *, struct dxcore_context *, struct nvc_driver_info *, const char *, int32_t, const char *); static int lookup_libraries(struct error *, struct dxcore_context *, struct nvc_driver_info *, const char *, int32_t, const char *); @@ -310,7 +309,7 @@ find_binary_paths(struct error *err, struct dxcore_context* dxcore, struct nvc_d return (rv); } -static int +int find_device_node(struct error *err, const char *root, const char *dev, struct nvc_device_node *node) { char path[PATH_MAX]; diff --git a/src/nvc_internal.h b/src/nvc_internal.h index cd1d785b3..3b0a4797f 100644 --- a/src/nvc_internal.h +++ b/src/nvc_internal.h @@ -37,6 +37,8 @@ #define NV_MODESET_DEVICE_PATH _PATH_DEV "nvidia-modeset" #define NV_CAPS_DEVICE_DIR _PATH_DEV "nvidia-caps" #define NV_CAPS_DEVICE_PATH NV_CAPS_DEVICE_DIR "/nvidia-cap%d" +#define NV_CAPS_IMEX_DEVICE_DIR _PATH_DEV "nvidia-caps-imex-channels" +#define NV_CAPS_IMEX_DEVICE_PATH NV_CAPS_IMEX_DEVICE_DIR "/channel%d" #define NV_PERSISTENCED_SOCKET _PATH_VARRUN "nvidia-persistenced/socket" #define NV_FABRICMANAGER_SOCKET _PATH_VARRUN "nvidia-fabricmanager/socket" #define NV_MPS_PIPE_DIR _PATH_TMP "nvidia-mps" @@ -174,5 +176,6 @@ void nvc_entrypoint(void); /* Prototypes from nvc_info.c */ bool match_binary_flags(const char *, int32_t); bool match_library_flags(const char *, int32_t); +int find_device_node(struct error *, const char *, const char *, struct nvc_device_node *); #endif /* HEADER_NVC_INTERNAL_H */ diff --git a/src/nvc_mount.c b/src/nvc_mount.c index 835437124..6b2d0bb57 100644 --- a/src/nvc_mount.c +++ b/src/nvc_mount.c @@ -1092,3 +1092,53 @@ nvc_device_mig_caps_mount(struct nvc_context *ctx, const struct nvc_container *c return (rv); } + +int +nvc_imex_channel_mount(struct nvc_context *ctx, const struct nvc_container *cnt, const struct nvc_imex_channel *chan) +{ + // Initialize local variables. + char path[PATH_MAX]; + struct nvc_device_node node; + char *mnt = NULL; + int rv = -1; + + // Validate incoming arguments. + if (validate_context(ctx) < 0) + return (-1); + if (validate_args(ctx, cnt != NULL && chan != NULL) < 0) + return (-1); + + // Enter the mount namespace of the container. + if (ns_enter(&ctx->err, cnt->mnt_ns, CLONE_NEWNS) < 0) + return (-1); + + // Construct a device node for the channel. + if (xsnprintf(&ctx->err, path, sizeof(path), NV_CAPS_IMEX_DEVICE_PATH, chan->id) < 0) + goto fail; + if (find_device_node(&ctx->err, ctx->cfg.root, path, &node) < 0) + goto fail; + + // Mount the device node and set up its cgroups (as appropriate). + if (!(cnt->flags & OPT_NO_DEVBIND)) { + if ((mnt = mount_device(&ctx->err, ctx->cfg.root, cnt, &node)) == NULL) + goto fail; + } + if (!(cnt->flags & OPT_NO_CGROUPS)) { + if (setup_device_cgroup(&ctx->err, cnt, node.id) < 0) + goto fail; + } + + // Set the return value to indicate success. + rv = 0; + + fail: + if (rv < 0) { + unmount(mnt); + assert_func(ns_enter_at(NULL, ctx->mnt_ns, CLONE_NEWNS)); + } else { + rv = ns_enter_at(&ctx->err, ctx->mnt_ns, CLONE_NEWNS); + } + free(mnt); + + return (rv); +} diff --git a/src/utils.c b/src/utils.c index 8ff6f2693..ed35c0d3a 100644 --- a/src/utils.c +++ b/src/utils.c @@ -213,6 +213,24 @@ str_join(struct error *err, char **s1, const char *s2, const char *sep) return (0); } +size_t +str_count_tokens(const char *s, char sep) +{ + size_t count = 0; + + // No tokens if s == NULL or len(s) == 0 + if (s == NULL || s[0] == '\0') + return (0); + + // Count the number of delimeters + for (int i = 1; s[i] != '\0'; i++) + if (s[i] == sep) + count++; + + // Add 1 to count the last item after the final delimiter + return (count + 1); +} + int str_to_pid(struct error *err, const char *str, pid_t *pid) { diff --git a/src/utils.h b/src/utils.h index ba8170f31..e9cedf1a8 100644 --- a/src/utils.h +++ b/src/utils.h @@ -54,6 +54,7 @@ bool str_array_match(const char *, const char * const [], size_t); int str_to_pid(struct error *, const char *, pid_t *); int str_to_ugid(struct error *, char *, uid_t *, gid_t *); int str_join(struct error *, char **, const char *, const char *); +size_t str_count_tokens(const char *, char); int ns_enter_at(struct error *, int, int); int ns_enter(struct error *, const char *, int);