The goal of the code is to convert a Graphviz DOT file to an SVG file, and it achieves this by creating a child process and executing the "dot" command.
#include <stdio.h>
#include <stdlib.h>
#if defined(_WIN32)
#include <windows.h>
#define OS_WINDOWS 1
#elif defined(__unix__) || defined(__unix) || defined(__linux__) || (defined(__APPLE__) && defined(__MACH__)))
#include <sys/wait.h>
#include <unistd.h>
#define OS_UNIX 1
#endif
int main(void)
{
#if defined(OS_UNIX)
const int pid = fork();
if (pid == -1) {
fprintf(stderr, "Error: could not fork a child: %s\n",
strerror(errno));
return EXIT_FAILURE;
} else if (pid == 0) {
static char *const args[] = {"dot", "-Tsvg", "trie.dot", "-o", "trie.svg", (char *)NULL};
if (execv("/usr/bin/dot", args) == -1) {
fprintf(stderr, "Error: could not execute the child: %s\n",
sterror(errno));
_Exit(EXIT_FAILURE);
}
} else {
int rv;
if (wait(&rv) == -1) {
fprintf(stderr, "Error: could not wait for the forked child: %s\n",
sterror(errno));
return EXIT_FAILURE;
}
if (!WIFEXITED(rv)) {
fprintf(stderr, "Error: child exited with an error status: %d\n", WEXITSTATUS(rv);
return EXIT_FAILURE;
}
}
#else
STARTUPINFO si;
PROCESS_INFORMATION pi;
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
ZeroMemory(&pi, sizeof(pi));
static char *const cmd= "dot -Tsvg trie.dot -o trie.svg";
if (!CreateProcess(NULL, cmd, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi)) {
fprintf(stderr, "CreateProcess() failed (%d).\n", GetLastError());
return EXIT_FAILURE;
}
const DWORD rv = WaitForSingleObject(pi.hProcess, INFINITE);
switch(rv) {
case WAIT_ABANDONED:
fprintf(stderr, "Error: mutex object was not released by the thread that"
"owned the mutex object before the owning thread terminated.\n");
return EXIT_FAILURE;
case WAIT_OBJECT_0:
fprintf(stderr, "Error: the child thread state was signaled.\n");
return EXIT_FAILURE;
case WAIT_TIMEOUT:
fprintf(stderr, "Error: time-out interval elapsed, and the child thread's state is nonsignaled.\n");
return EXIT_FAILURE;
case WAIT_FAILED:
fprintf(stderr, "Error: WaitForSingleObject() failed, error status: %d.\n", GetLastError());
return EXIT_FAILURE;
}
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
return EXIT_SUCCESS;
#endif
}
The code compiles successfully on both Windows 10 and Linux Mint 21, which serves my purpose.
Review request:
- Are there any simplifications or improvements that can be made to the code?
- Is the current implementation sufficient for its intended purpose, or are there potential pitfalls that need addressing?
- Is the error-handling approach used in the code appropriate?
Edit: The code is part of a larger command-line program, which has to be ported over to Windows. The only thing non-portable about the program is fork()
and exec()
.
posix_spawn
combines fork/exec, and can be more efficient on stuff like Cygwin where you have a POSIX API on top of an OS that has CreateProcess, not fork. man7.org/linux/man-pages/man3/posix_spawn.3.html - and it's just one syscall to check errors from. POSIX.2008 \$\endgroup\$posix_spawnp
uses$PATH
like execvp.posix_spawn
doesn't, like execv. You were usingexecv
so I used the non-p
version. It works for me on my Arch Linux desktop. (When you say "relative path", I think you mean using PATH. But relative path actually means relative to the current directory. If Iln -s /bin/ls .
so./ls
is a valid path to an executable, your code withposix_spawn
runs it. Butposix_spawnp
doesn't because I don't include the current directory in my$PATH
. (I changed it fromls
tofoobar
so it wouldn't find it there either.) \$\endgroup\$sterror
bestrerror
? also missing one parentheses at end of line ` fprintf(stderr, "Error: child exited with an error status: %d\n", WEXITSTATUS(rv);` ? \$\endgroup\$fork()
returns apid_t
, why assign the function result to anint
? \$\endgroup\$