ProvSQL C/C++ API
Adding support for provenance and uncertainty management to PostgreSQL databases
Loading...
Searching...
No Matches
external_tool.cpp
Go to the documentation of this file.
1/**
2 * @file external_tool.cpp
3 * @brief Implementation of the external-tool helpers.
4 *
5 * Reads the @c provsql.tool_search_path GUC (exposed as
6 * @c provsql_tool_search_path) and uses it both to extend @c $PATH around
7 * @c system() and to drive the pre-flight @c find_external_tool() lookup.
8 */
9extern "C" {
10#include "postgres.h"
11#include "provsql_utils.h"
12#include "miscadmin.h"
13
14#include <signal.h>
15#include <stdlib.h>
16#include <unistd.h>
17#include <sys/wait.h>
18}
19
20#include "external_tool.h"
21
22#include <string>
23
24// PATH that /bin/sh resolves binaries against when the environment has no
25// PATH set. PostgreSQL backends inherit no PATH from systemd, so
26// getenv("PATH") is NULL inside the server; without an explicit fallback,
27// setting PATH to "<GUC>" alone would mask /usr/local/bin and friends
28// (dash's compiled-in default), making the GUC-set case strictly narrower
29// than the GUC-empty case. Matches dash's _PATH_STDPATH on Debian/Ubuntu
30// and bash's default on macOS.
31static const char *DEFAULT_PATH =
32 "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin";
33
34int run_external_tool(const std::string &cmdline) {
35 bool override_path = (provsql_tool_search_path != NULL
36 && provsql_tool_search_path[0] != '\0');
37 std::string saved_path;
38 bool had_path = false;
39
40 if (override_path) {
41 const char *cur = getenv("PATH");
42 if (cur != NULL) {
43 saved_path = cur;
44 had_path = true;
45 }
46 std::string new_path(provsql_tool_search_path);
47 new_path += ':';
48 new_path += had_path ? saved_path : DEFAULT_PATH;
49 setenv("PATH", new_path.c_str(), 1);
50 }
51
52 int rv = system(cmdline.c_str());
53
54 if (override_path) {
55 if (had_path)
56 setenv("PATH", saved_path.c_str(), 1);
57 else
58 unsetenv("PATH");
59 }
60
61 return rv;
62}
63
64std::string find_external_tool(const std::string &name) {
65 // Path-like names (containing '/') are tested directly without any
66 // search-path walk; this matches POSIX execvp semantics.
67 if (name.find('/') != std::string::npos)
68 return access(name.c_str(), X_OK) == 0 ? name : "";
69
70 // Delegate the search to /bin/sh via `command -v`, routed through
71 // run_external_tool() so the GUC override is honoured. This reuses
72 // exactly the PATH resolution that the eventual tool invocation will
73 // see, including the shell's compiled-in default when the environment
74 // has no PATH (typical inside a PostgreSQL backend).
75 //
76 // Single-quoting `name` defends against shell metacharacters; the
77 // five tool names provsql actually uses ("d4", "c2d", "minic2d",
78 // "dsharp", "weightmc", "graph-easy") contain none.
79 std::string check = "command -v '" + name + "' >/dev/null 2>&1";
80 int rv = run_external_tool(check);
81
82 // If statement_timeout (or any cancel) fired while command -v was
83 // running, the SIGINT kills the child but glibc's system() SIG_IGNs it
84 // in the parent, leaving InterruptPending unset. Without this
85 // translation, the empty return below would surface as "tool not
86 // found on PATH" (XX000), which the d4_timeout test's
87 // EXCEPTION WHEN query_canceled clause cannot catch. Mirrors the
88 // post-system() recovery pattern used after the actual compiler call
89 // in BooleanCircuit::compilation.
90 if (WIFSIGNALED(rv) && WTERMSIG(rv) == SIGINT) {
91 InterruptPending = true;
92 QueryCancelPending = true;
93 CHECK_FOR_INTERRUPTS();
94 }
95
96 return rv == 0 ? name : "";
97}
98
99std::string format_external_tool_status(int rv, const std::string &tool) {
100 if (rv == 0)
101 return "";
102 if (rv == -1)
103 return tool + " could not be invoked (system() returned -1)";
104 if (WIFSIGNALED(rv))
105 return tool + " terminated by signal "
106 + std::to_string(WTERMSIG(rv));
107 if (WIFEXITED(rv)) {
108 int code = WEXITSTATUS(rv);
109 if (code == 127)
110 return tool + " was not found at runtime (shell exit 127); "
111 "install it or add its directory to provsql.tool_search_path";
112 if (code == 126)
113 return tool + " is not executable (shell exit 126); "
114 "check permissions on the binary";
115 return tool + " exited with status " + std::to_string(code);
116 }
117 return tool + " failed with raw status " + std::to_string(rv);
118}
int run_external_tool(const std::string &cmdline)
Run a shell command line, optionally extending PATH.
std::string format_external_tool_status(int rv, const std::string &tool)
Decode a system() return value into a human-readable message.
std::string find_external_tool(const std::string &name)
Locate an external tool by name.
static const char * DEFAULT_PATH
Helpers for invoking external command-line tools.
char * provsql_tool_search_path
Colon-separated directory list prepended to PATH when invoking external tools (d4,...
Definition provsql.c:80
Core types, constants, and utilities shared across ProvSQL.