ascii-chat 0.11.33
Video chat in your terminal
Loading...
Searching...
No Matches
query.c
Go to the documentation of this file.
1
15#include <ascii-chat/common.h>
16
17#include <stdbool.h>
18#include <stdint.h>
19#include <stdio.h>
20#include <stdlib.h>
21#include <string.h>
22
23#ifdef _WIN32
24#define WIN32_LEAN_AND_MEAN
25#include <windows.h>
26#include <winsock2.h>
27#pragma comment(lib, "ws2_32.lib")
28#else
29#include <arpa/inet.h>
30#include <errno.h>
31#include <netinet/in.h>
32#include <signal.h>
33#include <sys/socket.h>
34#include <sys/types.h>
35#include <sys/wait.h>
36#include <unistd.h>
37#endif
38
39// Timeouts for HTTP health check (in nanoseconds)
40#define HEALTH_CHECK_TIMEOUT_NS (10000LL * NS_PER_MS_INT)
41#define HEALTH_CHECK_INTERVAL_NS (100LL * NS_PER_MS_INT)
42#define HEALTH_CHECK_CONNECT_TIMEOUT_NS (500LL * NS_PER_MS_INT)
43
44// Global state for the query tool runtime
45static bool g_query_active = false;
46static int g_query_port = -1;
47
48#ifdef _WIN32
49static HANDLE g_controller_handle = NULL;
50static DWORD g_controller_pid = 0;
51#else
52static pid_t g_controller_pid = -1;
53#endif
54
61static bool try_http_connect(int port, int timeout_ms) {
62#ifdef _WIN32
63 SOCKET sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
64 if (sock == INVALID_SOCKET) {
65 return false;
66 }
67
68 // Set non-blocking mode for timeout
69 u_long mode = 1;
70 ioctlsocket(sock, FIONBIO, &mode);
71
72 struct sockaddr_in addr;
73 memset(&addr, 0, sizeof(addr));
74 addr.sin_family = AF_INET;
75 addr.sin_port = htons((u_short)port);
76 addr.sin_addr.s_addr = inet_addr("127.0.0.1");
77
78 connect(sock, (struct sockaddr *)&addr, sizeof(addr));
79
80 // Use select for timeout
81 fd_set writefds;
82 FD_ZERO(&writefds);
83 FD_SET(sock, &writefds);
84
85 struct timeval tv;
86 tv.tv_sec = timeout_ms / 1000;
87 tv.tv_usec = (timeout_ms % 1000) * 1000;
88
89 int result = select(0, NULL, &writefds, NULL, &tv);
90 closesocket(sock);
91
92 return result > 0;
93#else
94 int sock = socket(AF_INET, SOCK_STREAM, 0);
95 if (sock < 0) {
96 return false;
97 }
98
99 // Set socket timeout
100 struct timeval tv;
101 tv.tv_sec = timeout_ms / 1000;
102 tv.tv_usec = (timeout_ms % 1000) * 1000;
103 setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
104 setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));
105
106 struct sockaddr_in addr;
107 memset(&addr, 0, sizeof(addr));
108 addr.sin_family = AF_INET;
109 addr.sin_port = htons((uint16_t)port);
110 addr.sin_addr.s_addr = inet_addr("127.0.0.1");
111
112 int result = connect(sock, (struct sockaddr *)&addr, sizeof(addr));
113 close(sock);
114
115 return result == 0;
116#endif
117}
118
125static bool wait_for_http_ready(int port, uint64_t timeout_ns) {
126 uint64_t elapsed_ns = 0;
127
128 while (elapsed_ns < timeout_ns) {
129 if (try_http_connect(port, (int)(HEALTH_CHECK_CONNECT_TIMEOUT_NS / NS_PER_MS_INT))) {
130 return true;
131 }
132
133// Check if controller process is still alive
134#ifdef _WIN32
135 if (g_controller_handle != NULL) {
136 DWORD exit_code;
137 if (GetExitCodeProcess(g_controller_handle, &exit_code)) {
138 if (exit_code != STILL_ACTIVE) {
139 // Controller exited unexpectedly
140 return false;
141 }
142 }
143 }
144#else
145 if (g_controller_pid > 0) {
146 int status;
147 pid_t result = waitpid(g_controller_pid, &status, WNOHANG);
148 if (result == g_controller_pid) {
149 // Controller exited unexpectedly
150 g_controller_pid = -1;
151 return false;
152 }
153 }
154#endif
155
156 // Sleep between checks
158 elapsed_ns += HEALTH_CHECK_INTERVAL_NS;
159 }
160
161 return false;
162}
163
170static bool find_query_server_path(char *buffer, size_t buffer_size) {
171 // Try common locations relative to the executable
172 const char *search_paths[] = {// Relative to build directory
173 ".deps-cache/query-tool/ascii-query-server",
174 "../.deps-cache/query-tool/ascii-query-server",
175 "../../.deps-cache/query-tool/ascii-query-server",
176 // In PATH
177 "ascii-query-server",
178 // Absolute paths for development
179 NULL};
180
181 for (int i = 0; search_paths[i] != NULL; i++) {
182#ifdef _WIN32
183 // On Windows, append .exe
184 safe_snprintf(buffer, buffer_size, "%s.exe", search_paths[i]);
185 if (GetFileAttributesA(buffer) != INVALID_FILE_ATTRIBUTES) {
186 return true;
187 }
188#else
189 safe_snprintf(buffer, buffer_size, "%s", search_paths[i]);
190 if (access(buffer, X_OK) == 0) {
191 return true;
192 }
193#endif
194 }
195
196 // Try to find via environment variable
197 const char *query_server_path = SAFE_GETENV("ASCIICHAT_QUERY_SERVER");
198 if (query_server_path != NULL) {
199 safe_snprintf(buffer, buffer_size, "%s", query_server_path);
200#ifdef _WIN32
201 if (GetFileAttributesA(buffer) != INVALID_FILE_ATTRIBUTES) {
202 return true;
203 }
204#else
205 if (access(buffer, X_OK) == 0) {
206 return true;
207 }
208#endif
209 }
210
211 return false;
212}
213
214int query_init(int preferred_port) {
215 // Already initialized?
216 if (g_query_active) {
217 return g_query_port;
218 }
219
220 // Find the query server executable
221 char server_path[PLATFORM_MAX_PATH_LENGTH];
222 if (!find_query_server_path(server_path, sizeof(server_path))) {
223 fprintf(stderr, "[query] Could not find ascii-query-server executable\n");
224 fprintf(stderr, "[query] Set ASCIICHAT_QUERY_SERVER environment variable or ensure "
225 "it's in .deps-cache/query-tool/\n");
226 return -1;
227 }
228
229#ifdef _WIN32
230 // Windows implementation using CreateProcess
231 // Winsock is already initialized by platform_init()
232
233 char cmdline[2048];
234 safe_snprintf(cmdline, sizeof(cmdline), "\"%s\" --attach %lu --port %d", server_path,
235 (unsigned long)GetCurrentProcessId(), preferred_port);
236
237 STARTUPINFOA si;
238 PROCESS_INFORMATION pi;
239 memset(&si, 0, sizeof(si));
240 si.cb = sizeof(si);
241 memset(&pi, 0, sizeof(pi));
242
243 // Create the controller process
244 if (!CreateProcessA(NULL, // Use command line
245 cmdline, // Command line
246 NULL, // Process security attributes
247 NULL, // Thread security attributes
248 FALSE, // Don't inherit handles
249 CREATE_NEW_CONSOLE, // Creation flags
250 NULL, // Use parent's environment
251 NULL, // Use parent's directory
252 &si, &pi)) {
253 fprintf(stderr, "[query] Failed to start query server: error %lu\n", GetLastError());
254 return -1;
255 }
256
257 g_controller_handle = pi.hProcess;
258 g_controller_pid = pi.dwProcessId;
259 CloseHandle(pi.hThread); // Don't need the thread handle
260
261 fprintf(stderr, "[query] Started query server (PID %lu) on port %d\n", (unsigned long)g_controller_pid,
262 preferred_port);
263
264#else
265 // Unix implementation using fork/exec
266 pid_t self_pid = getpid();
267
268 char port_str[16];
269 char pid_str[16];
270 safe_snprintf(port_str, sizeof(port_str), "%d", preferred_port);
271 safe_snprintf(pid_str, sizeof(pid_str), "%d", self_pid);
272
273 pid_t child = fork();
274 if (child < 0) {
275 fprintf(stderr, "[query] fork() failed: %s\n", strerror(errno));
276 return -1;
277 }
278
279 if (child == 0) {
280 // Child process: exec the controller
281 // Redirect stdout/stderr to /dev/null or a log file to avoid clutter
282 // (The controller has its own logging)
283
284 execl(server_path, "ascii-query-server", "--attach", pid_str, "--port", port_str, (char *)NULL);
285
286 // If exec fails
287 fprintf(stderr, "[query] exec(%s) failed: %s\n", server_path, strerror(errno));
288 exit(1);
289 }
290
291 // Parent process
292 g_controller_pid = child;
293 fprintf(stderr, "[query] Started query server (PID %d) on port %d\n", child, preferred_port);
294#endif
295
296 // Wait for the HTTP server to become ready
297 fprintf(stderr, "[query] Waiting for HTTP server to be ready...\n");
298 if (!wait_for_http_ready(preferred_port, HEALTH_CHECK_TIMEOUT_NS)) {
299 fprintf(stderr, "[query] Timeout waiting for query server to start\n");
301 return -1;
302 }
303
304 g_query_active = true;
305 g_query_port = preferred_port;
306
307 fprintf(stderr, "[query] Query server ready at http://localhost:%d\n", preferred_port);
308 return preferred_port;
309}
310
311void query_destroy(void) {
312 if (!g_query_active && g_controller_pid <= 0) {
313 return;
314 }
315
316 fprintf(stderr, "[query] Shutting down query server...\n");
317
318#ifdef _WIN32
319 if (g_controller_handle != NULL) {
320 // Send termination signal
321 TerminateProcess(g_controller_handle, 0);
322
323 // Wait for process to exit (with timeout)
324 WaitForSingleObject(g_controller_handle, 3000);
325
326 CloseHandle(g_controller_handle);
327 g_controller_handle = NULL;
328 g_controller_pid = 0;
329 }
330
331 // Winsock cleanup handled by platform_cleanup()
332#else
333 if (g_controller_pid > 0) {
334 // Send SIGTERM for graceful shutdown
335 kill(g_controller_pid, SIGTERM);
336
337 // Wait for process to exit (with timeout)
338 int status;
339 int wait_count = 0;
340 while (wait_count < 30) { // 3 second timeout
341 pid_t result = waitpid(g_controller_pid, &status, WNOHANG);
342 if (result == g_controller_pid) {
343 break;
344 }
345 if (result < 0) {
346 break;
347 }
348 usleep(100 * US_PER_MS_INT); // 100ms
349 wait_count++;
350 }
351
352 // If still running, force kill
353 if (wait_count >= 30) {
354 kill(g_controller_pid, SIGKILL);
355 waitpid(g_controller_pid, &status, 0);
356 }
357
358 g_controller_pid = -1;
359 }
360#endif
361
362 g_query_active = false;
363 g_query_port = -1;
364
365 fprintf(stderr, "[query] Query server stopped\n");
366}
367
368bool query_is_active(void) {
369 if (!g_query_active) {
370 return false;
371 }
372
373// Verify the controller is still running
374#ifdef _WIN32
375 if (g_controller_handle != NULL) {
376 DWORD exit_code;
377 if (GetExitCodeProcess(g_controller_handle, &exit_code)) {
378 if (exit_code != STILL_ACTIVE) {
379 g_query_active = false;
380 g_controller_handle = NULL;
381 g_controller_pid = 0;
382 return false;
383 }
384 }
385 }
386#else
387 if (g_controller_pid > 0) {
388 // Check if process is still alive
389 if (kill(g_controller_pid, 0) != 0) {
390 g_query_active = false;
391 g_controller_pid = -1;
392 return false;
393 }
394 }
395#endif
396
397 return g_query_active;
398}
399
400int query_get_port(void) {
401 return g_query_port;
402}
⚙️ Common definitions, error codes, macros, and types shared throughout the application
Cross-platform error handling utilities.
int buffer_size
Size of circular buffer.
Definition grep.c:90
unsigned short uint16_t
Definition common.h:57
#define SAFE_GETENV(name)
Definition common.h:434
unsigned long long uint64_t
Definition common.h:59
#define NS_PER_MS_INT
Definition time.h:156
#define US_PER_MS_INT
Definition time.h:160
int safe_snprintf(char *buffer, size_t buffer_size, const char *format,...)
Safe formatted string printing to buffer.
Definition system.c:148
void platform_sleep_ns(uint64_t ns)
Platform-safe sleep function with nanosecond precision.
int errno
int query_init(int preferred_port)
Initialize the query tool by spawning the controller process.
Definition query.c:214
void query_destroy(void)
Shutdown the query tool and terminate the controller process.
Definition query.c:311
bool query_is_active(void)
Check if the query tool controller is currently active.
Definition query.c:368
int query_get_port(void)
Get the port number of the active query server.
Definition query.c:400
#define HEALTH_CHECK_TIMEOUT_NS
Definition query.c:40
#define HEALTH_CHECK_INTERVAL_NS
Definition query.c:41
#define HEALTH_CHECK_CONNECT_TIMEOUT_NS
Definition query.c:42
Runtime variable query tool API for debug builds.
Cross-platform crash signal and exception handling.
ClangTool/LibTooling compatibility shim for stdbool.h.
#define PLATFORM_MAX_PATH_LENGTH
Definition system.c:69
⏱️ High-precision timing utilities using sokol_time.h and uthash