#fork() safe version of process.execv()

1 messages · Page 1 of 1 (latest)

sterile token
#

Looking for feedback.
I want to fork() and execv() but std.process.execv() does not like fork() due to heap allocation, so i built my own wrapper around std.posix.execve() which allocates some memory on the stack instead.

pub fn exec(argv: []const []const u8) !void {
    const max_args = 256;
    const max_arg_length = 2048;

    var args: [max_args][max_arg_length:0]u8 = undefined;
    var args_ptrs: [max_args:null]?[*:0]u8 = undefined;

    var i: usize = 0;
    for (argv) |arg| {
        @memcpy(&args[i], arg.ptr);
        args[i][arg.len] = 0; // add sentinel 0
        args_ptrs[i] = &args[i];
        i += 1;
    }
    args_ptrs[i] = null; // add sentinel null

    return std.posix.execvpeZ_expandArg0(.no_expand, args_ptrs[0].?, &args_ptrs, &.{null});
}

is this a good way or are there more idiomatic ways?

alpine cobalt
sterile token
#

yes

#

i am making calls to unshare();

#

before exec();

alpine cobalt
#

got it

sterile token
#

posix_spawn can't handle that unfortunately

alpine cobalt
#

i'd recommend allocating argv in the parent pre-fork

#

you could potentially copy the posix implementation of Child.spawn and modify it for your purposes

sterile token
#

dug into that already.. i think the only other way to retain the unshare() functionality would be with std.os.linux.clone

#

unshare() and clone() use the same flags

#

my question is probably more around the manipulation of the slices and converting them to what std.posix.execve wants. not sure if there's a way to "represent []const u8 as [0:*]const u8"

#

std.process.execve does this through the allocator's dupeZ etc.

#

generally i think clone() is probably what gives most control. and can already create the process in a new namespace

#

requries setting up the stack etc tho

alpine cobalt
sterile token
#

clone->execv vs fork->unshare->execv

alpine cobalt
#

then you just use slice.ptr to get it as [*:0]const u8

sterile token
#

well yah. i do controll the caller. but wanted to make the api similar to std.process.execv and generally use slices but then handle the complexity internally

alpine cobalt
#

using 0-terminated slices is fine; any string literals are already 0-terminated and most string generation strategies allow getting 0-terminated slices

sterile token
#

could do that and then handle guaranteeing the strings are actually 0 terminated elsewhere.

#

i think for now this will work. thank you for the feedback @alpine cobalt

alpine cobalt
#

then the compiler will ensure they're created correctly by giving you type errors :)

sterile token
#

they might be provided from the cmdline or via an http api. so i can not guarantee at compile time they're 0 terminated 🙂

alpine cobalt
#

command line args on posix are 0 terminated, and the type of std.posix.argv reflects that

#

in terms of the http api, you can either make your parsing code return terminated slices, or just dupeZ them

sterile token
#

will probably do the latter 🙂

#

just for context. building up a small container runtime from syscalls upwards... start by building a clone of unshare(1)

alpine cobalt
#

neat!

#

i'd love to see a #1024381264213594242 post about it at some point!

sterile token
#

yah, once i have something that can be shared without me getting code-shamed trollface

#

probably not going to make it OCI compliant. just trying to figure out what is the minimal set of features for it to be useful for most use-cases