Fix DN_OSExec not quoting CLI args on Windows

This commit is contained in:
2026-07-16 14:16:48 +10:00
parent eb48e06bdc
commit 2b6856b692
6 changed files with 150 additions and 198 deletions
+52 -10
View File
@@ -1,4 +1,4 @@
// Generated by the DN single header generator 2026-07-15 22:47:49
// Generated by the DN single header generator 2026-07-16 14:16:33
// DN: Single header generator commented out => #if defined(_CLANGD)
// #define DN_WITH_TESTS 1
@@ -15440,11 +15440,53 @@ DN_API DN_OSExecAsyncHandle DN_OS_ExecAsync(DN_Str8Slice cmd_line, DN_OSExecArgs
if (cmd_line.count == 0)
return result;
// NOTE: Render the command line into single string to pass into the Win32 API. We'll quote the
// string on behalf of the user if there's whitespace in the string
DN_TCScratch scratch = DN_TCScratchBeginArena(nullptr, 0);
DN_Str8 cmd_rendered = DN_Str8SliceRender(cmd_line, DN_Str8Lit(" "), &scratch.arena);
DN_Str16 cmd16 = DN_OS_W32Str8ToStr16(&scratch.arena, cmd_rendered);
DN_Str16 working_dir16 = DN_OS_W32Str8ToStr16(&scratch.arena, args->working_dir);
DN_Str8 cmd = {};
{
DN_USize count_req = 0;
DN_Str8 cmd_separator = DN_Str8Lit(" ");
for (DN_ForIt(it, DN_Str8, &cmd_line)) {
if (it.index)
count_req += cmd_separator.count;
if (DN_Str8Find(*it.data, DN_Str8FindFlag_Whitespace).found)
count_req += 2; // If there's whitespace, we'll quote the string for the user
count_req += it.data->count;
}
// NOTE: Build the string
cmd = DN_Str8AllocArena(count_req, DN_ZMem_No, &scratch.arena);
if (cmd.data) {
DN_USize write_index = 0;
for (DN_ForIt(it, DN_Str8, &cmd_line)) {
if (it.index) {
DN_Memcpy(cmd.data + write_index, cmd_separator.data, cmd_separator.count);
write_index += cmd_separator.count;
}
// NOTE: Quote the string if needed
DN_Str8 str8 = *it.data;
bool needs_quotes = DN_Str8Find(str8, DN_Str8FindFlag_Whitespace).found;
if (needs_quotes)
cmd.data[write_index++] = '\"';
// NOTE: Write contents
DN_Memcpy(cmd.data + write_index, str8.data, str8.count);
write_index += str8.count;
// NOTE: Close quote if needed
if (needs_quotes)
cmd.data[write_index++] = '\"';
}
DN_Assert(write_index == cmd.count);
}
}
DN_AssertF(cmd.count < DN_Kilobytes(32), "Command exceeds the allowable size in Win32");
DN_Str16 cmd16 = DN_OS_W32Str8ToStr16(&scratch.arena, cmd);
DN_Str16 working_dir16 = DN_OS_W32Str8ToStr16(&scratch.arena, args->working_dir);
DN_Str8Builder env_builder = DN_Str8BuilderFromArena(&scratch.arena);
DN_Str8BuilderAppendArrayRef(&env_builder, args->environment.data, args->environment.count);
if (env_builder.string_size)
@@ -15479,7 +15521,7 @@ DN_API DN_OSExecAsyncHandle DN_OS_ExecAsync(DN_Str8Slice cmd_line, DN_OSExecArgs
err,
result.os_error_code,
"Failed to create stdout pipe to redirect the output of the command '%.*s': %.*s",
DN_Str8PrintFmt(cmd_rendered),
DN_Str8PrintFmt(cmd),
DN_Str8PrintFmt(win_error.msg));
DN_TCScratchEnd(&scratch);
return result;
@@ -15492,14 +15534,14 @@ DN_API DN_OSExecAsyncHandle DN_OS_ExecAsync(DN_Str8Slice cmd_line, DN_OSExecArgs
result.os_error_code,
"Failed to make stdout 'read' pipe non-inheritable when trying to "
"execute command '%.*s': %.*s",
DN_Str8PrintFmt(cmd_rendered),
DN_Str8PrintFmt(cmd),
DN_Str8PrintFmt(win_error.msg));
DN_TCScratchEnd(&scratch);
return result;
}
}
// NOTE: Redirect stderr ///////////////////////////////////////////////////////////////////////
// NOTE: Redirect stderr
HANDLE stderr_read = {};
HANDLE stderr_write = {};
DN_DEFER
@@ -15522,7 +15564,7 @@ DN_API DN_OSExecAsyncHandle DN_OS_ExecAsync(DN_Str8Slice cmd_line, DN_OSExecArgs
err,
result.os_error_code,
"Failed to create stderr pipe to redirect the output of the command '%.*s': %.*s",
DN_Str8PrintFmt(cmd_rendered),
DN_Str8PrintFmt(cmd),
DN_Str8PrintFmt(win_error.msg));
DN_TCScratchEnd(&scratch);
return result;
@@ -15535,7 +15577,7 @@ DN_API DN_OSExecAsyncHandle DN_OS_ExecAsync(DN_Str8Slice cmd_line, DN_OSExecArgs
result.os_error_code,
"Failed to make stderr 'read' pipe non-inheritable when trying to "
"execute command '%.*s': %.*s",
DN_Str8PrintFmt(cmd_rendered),
DN_Str8PrintFmt(cmd),
DN_Str8PrintFmt(win_error.msg));
DN_TCScratchEnd(&scratch);
return result;
@@ -15564,7 +15606,7 @@ DN_API DN_OSExecAsyncHandle DN_OS_ExecAsync(DN_Str8Slice cmd_line, DN_OSExecArgs
if (!create_result) {
DN_OSW32Error win_error = DN_OS_W32LastError(&scratch.arena);
result.os_error_code = win_error.code;
DN_ErrSinkAppendF(err, result.os_error_code, "Failed to execute command '%.*s': %.*s", DN_Str8PrintFmt(cmd_rendered), DN_Str8PrintFmt(win_error.msg));
DN_ErrSinkAppendF(err, result.os_error_code, "Failed to execute command '%.*s': %.*s", DN_Str8PrintFmt(cmd), DN_Str8PrintFmt(win_error.msg));
DN_TCScratchEnd(&scratch);
return result;
}
+1 -1
View File
@@ -1,4 +1,4 @@
// Generated by the DN single header generator 2026-07-15 22:47:49
// Generated by the DN single header generator 2026-07-16 14:16:33
#if !defined(DN_H)
#define DN_H
+5 -134
View File
@@ -1,81 +1,5 @@
#include "dn_seshc.h"
struct DN_SHSendDMThreadContext_
{
DN_Arena arena;
DN_SHCLIArgs args;
DN_Str8 sesh_pkey_hex;
DN_Str8 msg;
};
void DN_SH_LogBroadcast(DN_LogType type, DN_SHCLIArgs *cli_args, DN_Str8 short_desc, char const *fmt, ...)
{
DN_TCScratch scratch = DN_TCScratchBeginArena(nullptr, 0);
va_list args;
va_start(args, fmt);
DN_Str8 msg = DN_Str8FromFmtVArena(&scratch.arena, fmt, args);
va_end(args);
DN_Str8 prefix = {};
switch (type) {
case DN_LogType_Debug: prefix = DN_Str8Lit("🐞 "); break;
case DN_LogType_Info: prefix = DN_Str8Lit(""); break;
case DN_LogType_Warning: prefix = DN_Str8Lit("⚠️ "); break;
case DN_LogType_Error: prefix = DN_Str8Lit("🚨 "); break;
case DN_LogType_Count: break;
}
DN_LogF(type, "%.*s%.*s", DN_Str8PrintFmt(prefix), DN_Str8PrintFmt(msg));
DN_Date date = DN_OS_DateLocalTimeNow();
// NOTE: 12-hour conversion
DN_U8 hour12 = date.hour % 12;
if (hour12 == 0)
hour12 = 12;
char const *am_pm = date.hour < 12 ? "AM" : "PM";
// Time-of-day emoji
DN_Str8 time_emoji = {};
if (date.hour >= 5 && date.hour <= 11)
time_emoji = DN_Str8Lit("☀️");
else if (date.hour >= 12 && date.hour <= 16)
time_emoji = DN_Str8Lit("🌤️");
else if (date.hour >= 17 && date.hour <= 19)
time_emoji = DN_Str8Lit("🌅");
else if (date.hour >= 20 && date.hour <= 23)
time_emoji = DN_Str8Lit("");
else
time_emoji = DN_Str8Lit("🌑");
DN_SH_CLIArgsSendDMDetachedFmt(cli_args,
cli_args->recipient_pkey,
"%.*s %.*s\n"
"%.*s %u-%02u-%02u %u:%02u:%02u %s\n"
"%.*s",
DN_Str8PrintFmt(prefix),
DN_Str8PrintFmt(short_desc),
DN_Str8PrintFmt(time_emoji),
date.year,
date.month,
date.day,
hour12,
date.minutes,
date.seconds,
am_pm,
DN_Str8PrintFmt(msg));
DN_TCScratchEnd(&scratch);
}
DN_SHCLIArgs DN_SH_CLIArgsCopy(DN_SHCLIArgs const *src, DN_Arena *arena)
{
DN_SHCLIArgs result = {};
result = *src;
result.exe_path = DN_Str8FromStr8Arena(src->exe_path, arena);
result.account_secret = DN_Str8FromStr8Arena(src->account_secret, arena);
result.seed_node_url = DN_Str8FromStr8Arena(src->seed_node_url, arena);
result.display_name = DN_Str8FromStr8Arena(src->display_name, arena);
result.recipient_pkey = DN_Str8FromStr8Arena(src->recipient_pkey, arena);
return result;
}
DN_Str8 DN_SH_CLIOptionsStr8()
{
DN_Str8 result = DN_Str8Lit(
@@ -153,10 +77,8 @@ DN_SHCLIEatArgResult DN_SH_CLIEatArg(DN_SHCLIArgs *args, char const **arg_ptr, i
return result;
}
bool DN_SH_CLIArgsSendDMBlockingFmt(DN_SHCLIArgs *cli_args, DN_Str8 sesh_pkey_hex, char const *fmt, ...)
bool DN_SH_CLIArgsSendDMFmt(DN_SHCLIArgs *cli_args, DN_Str8 sesh_pkey_hex, char const *fmt, ...)
{
bool result = false;
if (cli_args->valid) {
DN_TCScratch scratch = DN_TCScratchBeginArena(nullptr, 0);
va_list args;
@@ -164,14 +86,6 @@ bool DN_SH_CLIArgsSendDMBlockingFmt(DN_SHCLIArgs *cli_args, DN_Str8 sesh_pkey_he
DN_Str8 msg = DN_Str8FromFmtVArena(&scratch.arena, fmt, args);
va_end(args);
// NOTE: Escape quotes and quote the input strings
DN_Str8 display_name = cli_args->display_name;
display_name = DN_Str8ReplaceSensitive(display_name, DN_Str8Lit("\""), DN_Str8Lit("\\\""), 0, &scratch.arena);
display_name = DN_Str8FromFmtArena(&scratch.arena, "\"%.*s\"", DN_Str8PrintFmt(display_name));
msg = DN_Str8ReplaceSensitive(msg, DN_Str8Lit("\""), DN_Str8Lit("\\\""), 0, &scratch.arena);
msg = DN_Str8FromFmtArena(&scratch.arena, "\"%.*s\"", DN_Str8PrintFmt(msg));
DN_Str8 cmds[] = {
cli_args->exe_path,
DN_Str8Lit("--account-secret"),
@@ -179,7 +93,7 @@ bool DN_SH_CLIArgsSendDMBlockingFmt(DN_SHCLIArgs *cli_args, DN_Str8 sesh_pkey_he
DN_Str8Lit("--seed-node-url"),
cli_args->seed_node_url,
DN_Str8Lit("--display-name"),
display_name,
DN_Str8FromFmtArena(&scratch.arena, "\"%.*s\"", DN_Str8PrintFmt(cli_args->display_name)),
DN_Str8Lit("send"),
sesh_pkey_hex,
msg,
@@ -191,54 +105,11 @@ bool DN_SH_CLIArgsSendDMBlockingFmt(DN_SHCLIArgs *cli_args, DN_Str8 sesh_pkey_he
DN_OSExecArgs exec_args = {};
exec_args.flags |= DN_OSExecFlags_SaveOutput;
DN_OSExecResult exec = DN_OS_Exec(cmd_line, &exec_args, &scratch.arena, nullptr);
result = exec.os_error_code == 0 && exec.exit_code == 0;
if (!result) {
DN_LogErrorF("Error sending message to %.*s (exit code: %d, os exit code: %d): %.*s",
DN_Str8PrintFmt(cli_args->recipient_pkey),
exec.exit_code,
exec.os_error_code,
DN_Str8PrintFmt(exec.stderr_text));
}
DN_LogInfoF("OUT (exit code:%zu, os: %zu): %.*s", exec.exit_code, exec.os_error_code, DN_Str8PrintFmt(exec.stdout_text));
DN_LogInfoF("ERR: %.*s", DN_Str8PrintFmt(exec.stderr_text));
DN_TCScratchEnd(&scratch);
}
bool result = exec.os_error_code == 0 && exec.exit_code == 0;
return result;
}
static int DN_SH_CLIArgsSendDMDetachedFmtThreadFunc_(DN_OSThread *thread)
{
DN_SHSendDMThreadContext_ *context = DN_Cast(DN_SHSendDMThreadContext_ *) thread->user_context;
DN_SH_CLIArgsSendDMBlockingFmt(&context->args, context->sesh_pkey_hex, "%.*s", DN_Str8PrintFmt(context->msg));
DN_ArenaDeinit(&context->arena);
return 0;
}
void DN_SH_CLIArgsSendDMDetachedFmt(DN_SHCLIArgs *cli_args, DN_Str8 sesh_pkey_hex, char const *fmt, ...)
{
if (!cli_args->valid)
return;
// NOTE: Setup context
DN_Arena arena = DN_OS_ArenaFromHeapVirtual(0, 0, DN_MemFlags_Nil);
DN_SHSendDMThreadContext_ *context = DN_ArenaNewZ(&arena, DN_SHSendDMThreadContext_);
context->args = DN_SH_CLIArgsCopy(cli_args, &arena);
context->sesh_pkey_hex = DN_Str8FromStr8Arena(sesh_pkey_hex, &arena);
va_list args;
va_start(args, fmt);
context->msg = DN_Str8FromFmtVArena(&arena, fmt, args);
va_end(args);
// NOTE: Set the arena last after all allocations
context->arena = arena;
// NOTE: Setup thread to be detached
DN_OSThread thread = {};
DN_OSThreadInitArgs thread_args = DN_OS_ThreadInitArgsDefault();
thread_args.flags |= DN_OSThreadFlags_Detached;
// NOTE: Execute and forget
DN_OS_ThreadInit(&thread, DN_SH_CLIArgsSendDMDetachedFmtThreadFunc_, thread_args, context);
}
+1 -4
View File
@@ -29,11 +29,8 @@ struct DN_SHCLIEatArgResult
// Overview
// Helper library that provides some useful APIs for programatically calling the Seshc CLI
// executable to send messages from your C/C++ application.
void DN_SH_LogBroadcast (DN_LogType type, DN_SHCLIArgs *cli_args, DN_Str8 short_desc, char const *fmt, ...);
DN_SHCLIArgs DN_SH_CLIArgsCopy (DN_SHCLIArgs const *src, DN_Arena *arena);
DN_Str8 DN_SH_CLIOptionsStr8 ();
DN_SHCLIEatArgResult DN_SH_CLIEatArg (DN_SHCLIArgs *args, char const **arg_ptr, int arg_count);
bool DN_SH_CLIArgsSendDMBlockingFmt(DN_SHCLIArgs *cli_args, DN_Str8 sesh_pkey_hex, char const *fmt, ...);
void DN_SH_CLIArgsSendDMDetachedFmt(DN_SHCLIArgs *cli_args, DN_Str8 sesh_pkey_hex, char const *fmt, ...);
bool DN_SH_CLIArgsSendDMFmt(DN_SHCLIArgs *cli_args, DN_Str8 sesh_pkey_hex, char const *fmt, ...);
#endif // #if !defined(DN_SESHC_H)
+51 -9
View File
@@ -854,11 +854,53 @@ DN_API DN_OSExecAsyncHandle DN_OS_ExecAsync(DN_Str8Slice cmd_line, DN_OSExecArgs
if (cmd_line.count == 0)
return result;
// NOTE: Render the command line into single string to pass into the Win32 API. We'll quote the
// string on behalf of the user if there's whitespace in the string
DN_TCScratch scratch = DN_TCScratchBeginArena(nullptr, 0);
DN_Str8 cmd_rendered = DN_Str8SliceRender(cmd_line, DN_Str8Lit(" "), &scratch.arena);
DN_Str16 cmd16 = DN_OS_W32Str8ToStr16(&scratch.arena, cmd_rendered);
DN_Str16 working_dir16 = DN_OS_W32Str8ToStr16(&scratch.arena, args->working_dir);
DN_Str8 cmd = {};
{
DN_USize count_req = 0;
DN_Str8 cmd_separator = DN_Str8Lit(" ");
for (DN_ForIt(it, DN_Str8, &cmd_line)) {
if (it.index)
count_req += cmd_separator.count;
if (DN_Str8Find(*it.data, DN_Str8FindFlag_Whitespace).found)
count_req += 2; // If there's whitespace, we'll quote the string for the user
count_req += it.data->count;
}
// NOTE: Build the string
cmd = DN_Str8AllocArena(count_req, DN_ZMem_No, &scratch.arena);
if (cmd.data) {
DN_USize write_index = 0;
for (DN_ForIt(it, DN_Str8, &cmd_line)) {
if (it.index) {
DN_Memcpy(cmd.data + write_index, cmd_separator.data, cmd_separator.count);
write_index += cmd_separator.count;
}
// NOTE: Quote the string if needed
DN_Str8 str8 = *it.data;
bool needs_quotes = DN_Str8Find(str8, DN_Str8FindFlag_Whitespace).found;
if (needs_quotes)
cmd.data[write_index++] = '\"';
// NOTE: Write contents
DN_Memcpy(cmd.data + write_index, str8.data, str8.count);
write_index += str8.count;
// NOTE: Close quote if needed
if (needs_quotes)
cmd.data[write_index++] = '\"';
}
DN_Assert(write_index == cmd.count);
}
}
DN_AssertF(cmd.count < DN_Kilobytes(32), "Command exceeds the allowable size in Win32");
DN_Str16 cmd16 = DN_OS_W32Str8ToStr16(&scratch.arena, cmd);
DN_Str16 working_dir16 = DN_OS_W32Str8ToStr16(&scratch.arena, args->working_dir);
DN_Str8Builder env_builder = DN_Str8BuilderFromArena(&scratch.arena);
DN_Str8BuilderAppendArrayRef(&env_builder, args->environment.data, args->environment.count);
if (env_builder.string_size)
@@ -893,7 +935,7 @@ DN_API DN_OSExecAsyncHandle DN_OS_ExecAsync(DN_Str8Slice cmd_line, DN_OSExecArgs
err,
result.os_error_code,
"Failed to create stdout pipe to redirect the output of the command '%.*s': %.*s",
DN_Str8PrintFmt(cmd_rendered),
DN_Str8PrintFmt(cmd),
DN_Str8PrintFmt(win_error.msg));
DN_TCScratchEnd(&scratch);
return result;
@@ -906,14 +948,14 @@ DN_API DN_OSExecAsyncHandle DN_OS_ExecAsync(DN_Str8Slice cmd_line, DN_OSExecArgs
result.os_error_code,
"Failed to make stdout 'read' pipe non-inheritable when trying to "
"execute command '%.*s': %.*s",
DN_Str8PrintFmt(cmd_rendered),
DN_Str8PrintFmt(cmd),
DN_Str8PrintFmt(win_error.msg));
DN_TCScratchEnd(&scratch);
return result;
}
}
// NOTE: Redirect stderr ///////////////////////////////////////////////////////////////////////
// NOTE: Redirect stderr
HANDLE stderr_read = {};
HANDLE stderr_write = {};
DN_DEFER
@@ -936,7 +978,7 @@ DN_API DN_OSExecAsyncHandle DN_OS_ExecAsync(DN_Str8Slice cmd_line, DN_OSExecArgs
err,
result.os_error_code,
"Failed to create stderr pipe to redirect the output of the command '%.*s': %.*s",
DN_Str8PrintFmt(cmd_rendered),
DN_Str8PrintFmt(cmd),
DN_Str8PrintFmt(win_error.msg));
DN_TCScratchEnd(&scratch);
return result;
@@ -949,7 +991,7 @@ DN_API DN_OSExecAsyncHandle DN_OS_ExecAsync(DN_Str8Slice cmd_line, DN_OSExecArgs
result.os_error_code,
"Failed to make stderr 'read' pipe non-inheritable when trying to "
"execute command '%.*s': %.*s",
DN_Str8PrintFmt(cmd_rendered),
DN_Str8PrintFmt(cmd),
DN_Str8PrintFmt(win_error.msg));
DN_TCScratchEnd(&scratch);
return result;
@@ -978,7 +1020,7 @@ DN_API DN_OSExecAsyncHandle DN_OS_ExecAsync(DN_Str8Slice cmd_line, DN_OSExecArgs
if (!create_result) {
DN_OSW32Error win_error = DN_OS_W32LastError(&scratch.arena);
result.os_error_code = win_error.code;
DN_ErrSinkAppendF(err, result.os_error_code, "Failed to execute command '%.*s': %.*s", DN_Str8PrintFmt(cmd_rendered), DN_Str8PrintFmt(win_error.msg));
DN_ErrSinkAppendF(err, result.os_error_code, "Failed to execute command '%.*s': %.*s", DN_Str8PrintFmt(cmd), DN_Str8PrintFmt(win_error.msg));
DN_TCScratchEnd(&scratch);
return result;
}