solidc
Robust collection of general-purpose cross-platform C libraries and data structures designed for rapid and safe development in C
Loading...
Searching...
No Matches
thread.c
1#include "../include/thread.h"
2
3#include <limits.h>
4#include <stddef.h> // for NULL
5#include <stdio.h> // for perror, fprintf
6#include <stdlib.h> // for malloc, free
7#include <string.h> // for strerror
8#include <time.h> // for nanosleep
9
10#ifdef _WIN32
11#include <process.h> // for Windows threading
12#else
13#include <sys/syscall.h> // for syscall numbers
14#endif
15
16/* Platform-specific error code handling */
17#ifdef _WIN32
19#define MAX_WIN_ERROR 0x7FFFFFFF
20
26static inline int win_error_to_errno(DWORD win_error) {
27 // Prevent overflow when converting to int
28 if (win_error > MAX_WIN_ERROR) {
29 return EINVAL; // Use generic error for out-of-range values
30 }
31 // Map common Windows errors to POSIX equivalents where sensible
32 switch (win_error) {
33 case ERROR_NOT_ENOUGH_MEMORY:
34 case ERROR_OUTOFMEMORY:
35 return ENOMEM;
36 case ERROR_INVALID_PARAMETER:
37 case ERROR_INVALID_HANDLE:
38 return EINVAL;
39 case ERROR_ACCESS_DENIED:
40 return EACCES;
41 default:
42 return (int)win_error;
43 }
44}
45
51static inline int is_valid_thread_handle(HANDLE handle) { return (handle != NULL && handle != INVALID_HANDLE_VALUE); }
52#endif
53
54/* Windows-specific thread parameter wrapper */
55#ifdef _WIN32
57typedef struct {
58 ThreadStartRoutine start_routine;
59 void* data;
60 void* result;
61 volatile int started;
62} ThreadParams;
63
70DWORD WINAPI thread_start_wrapper(LPVOID lpParameter) {
71 if (lpParameter == NULL) {
72 return 1; // Indicate error
73 }
74
75 ThreadParams* params = (ThreadParams*)lpParameter;
76 ThreadStartRoutine start_routine = params->start_routine;
77 void* data = params->data;
78
79 // Signal that we've started and captured our parameters
80 params->started = 1;
81
82 if (start_routine == NULL) {
83 params->result = NULL;
84 return 1; // Indicate error
85 }
86
87 // Execute user's thread function and store result
88 params->result = start_routine(data);
89
90 // We don't free params here - it must live until thread_join or thread_detach
91 return 0; // Success
92}
93#endif
94
95int thread_create(Thread* thread, ThreadStartRoutine start_routine, void* data) {
96 if (thread == NULL || start_routine == NULL) {
97 return EINVAL;
98 }
99
100#ifdef _WIN32
101 ThreadParams* params = (ThreadParams*)malloc(sizeof(ThreadParams));
102 if (params == NULL) {
103 return ENOMEM;
104 }
105
106 params->start_routine = start_routine;
107 params->data = data;
108 params->result = NULL;
109 params->started = 0;
110
111 HANDLE handle = CreateThread(NULL, // Security attributes (default)
112 0, // Stack size (default)
113 thread_start_wrapper, // Thread start function
114 params, // Thread parameter
115 0, // Creation flags (run immediately)
116 NULL // Thread ID (not needed)
117 );
118
119 if (!is_valid_thread_handle(handle)) {
120 int error = win_error_to_errno(GetLastError());
121 free(params);
122 return error;
123 }
124
125 // Wait briefly for thread to capture parameters
126 // This prevents a race where the thread hasn't started yet
127 for (int i = 0; i < 1000 && !params->started; i++) {
128 Sleep(0); // Yield to allow thread to start
129 }
130
131 *thread = handle;
132 return 0;
133
134#else // POSIX
135 int ret = pthread_create(thread, NULL, start_routine, data);
136 return ret;
137#endif
138}
139
140int thread_create_attr(Thread* thread, ThreadAttr* attr, ThreadStartRoutine start_routine, void* data) {
141 if (thread == NULL || attr == NULL || start_routine == NULL) {
142 return EINVAL;
143 }
144
145#ifdef _WIN32
146 ThreadParams* params = (ThreadParams*)malloc(sizeof(ThreadParams));
147 if (params == NULL) {
148 return ENOMEM;
149 }
150
151 params->start_routine = start_routine;
152 params->data = data;
153 params->result = NULL;
154 params->started = 0;
155
156 HANDLE handle = CreateThread(&attr->sa, // Security attributes
157 attr->stackSize, // Stack size
158 thread_start_wrapper, // Thread start function
159 params, // Thread parameter
160 0, // Creation flags (run immediately)
161 NULL // Thread ID (not needed)
162 );
163
164 if (!is_valid_thread_handle(handle)) {
165 int error = win_error_to_errno(GetLastError());
166 free(params);
167 return error;
168 }
169
170 // Wait briefly for thread to capture parameters
171 for (int i = 0; i < 1000 && !params->started; i++) {
172 Sleep(0); // Yield to allow thread to start
173 }
174
175 *thread = handle;
176 return 0;
177
178#else // POSIX
179 int ret = pthread_create(thread, attr, start_routine, data);
180 return ret;
181#endif
182}
183
184int thread_join(Thread tid, void** retval) {
185#ifdef _WIN32
186 if (!is_valid_thread_handle(tid)) {
187 return EINVAL;
188 }
189
190 DWORD wait_result = WaitForSingleObject(tid, INFINITE);
191 if (wait_result != WAIT_OBJECT_0) {
192 return win_error_to_errno(GetLastError());
193 }
194
195 // Retrieve the thread's parameters to get the actual return value
196 // We stored the ThreadParams pointer in thread-local storage conceptually,
197 // but since we can't access it directly, we need a different approach.
198 //
199 // LIMITATION: On Windows, we cannot reliably retrieve the void* return value
200 // after the thread has exited without additional infrastructure (like storing
201 // ThreadParams in a global map). For now, we can only return the DWORD exit code.
202
203 if (retval != NULL) {
204 DWORD exit_code;
205 if (GetExitCodeThread(tid, &exit_code)) {
206 // Note: This loses information if the original return value was a 64-bit pointer
207 // This is a fundamental limitation of mapping POSIX thread API to Windows
208 *retval = (void*)(uintptr_t)exit_code;
209 } else {
210 *retval = NULL;
211 // Continue anyway - we successfully waited
212 }
213 }
214
215 CloseHandle(tid);
216 return 0;
217
218#else // POSIX
219 int ret = pthread_join(tid, retval);
220 return ret;
221#endif
222}
223
231void thread_exit(void* retval) {
232#ifdef _WIN32
233 // Cast pointer to DWORD (typically only lower 32 bits are meaningful on Windows)
234 ExitThread((DWORD)(uintptr_t)retval);
235#else
236 pthread_exit(retval);
237#endif
238}
239
241#ifdef _WIN32
242 if (!is_valid_thread_handle(tid)) {
243 return EINVAL;
244 }
245
246 // On Windows, "detaching" means we close our reference to the handle.
247 // The thread continues to run, and the system will clean up when it exits.
248 //
249 // CRITICAL DIFFERENCE FROM POSIX: After this call, the thread handle
250 // becomes invalid and cannot be used for any operations, including join.
251 // This matches POSIX semantics where you cannot join a detached thread.
252
253 if (!CloseHandle(tid)) {
254 return win_error_to_errno(GetLastError());
255 }
256 return 0;
257
258#else // POSIX
259 int ret = pthread_detach(tid);
260 return ret;
261#endif
262}
263
264#ifdef _WIN32
265DWORD thread_self() { return GetCurrentThreadId(); }
266#else
267pthread_t thread_self() { return pthread_self(); }
268#endif
269
270/* Thread Attribute Management */
271
273 if (attr == NULL) {
274 return EINVAL;
275 }
276
277#ifdef _WIN32
278 // Initialize to safe defaults with explicit zeroing for security
279 memset(attr, 0, sizeof(ThreadAttr));
280 attr->stackSize = 0; // Use system default stack size
281 attr->sa.nLength = sizeof(SECURITY_ATTRIBUTES);
282 attr->sa.lpSecurityDescriptor = NULL; // Default security
283 attr->sa.bInheritHandle = FALSE; // Don't inherit handles by default
284 return 0;
285
286#else // POSIX
287 int ret = pthread_attr_init(attr);
288 return ret;
289#endif
290}
291
293 if (attr == NULL) {
294 return EINVAL;
295 }
296
297#ifdef _WIN32
298 // Explicitly zero out the structure for security
299 memset(attr, 0, sizeof(ThreadAttr));
300 return 0;
301
302#else // POSIX
303 int ret = pthread_attr_destroy(attr);
304 return ret;
305#endif
306}
307
308/* Utility Functions */
309
310void sleep_ms(int ms) {
311 if (ms <= 0) {
312 return;
313 }
314
315#ifdef _WIN32
316 // Windows Sleep is documented to accept DWORD, validate range
317 if (ms < 0 || (unsigned int)ms > 0xFFFFFFFE) {
318 return; // Invalid range for Windows
319 }
320 Sleep((DWORD)ms);
321
322#else // POSIX
323 struct timespec ts;
324 ts.tv_sec = ms / 1000;
325 ts.tv_nsec = (ms % 1000) * 1000000L;
326
327 // Handle EINTR by continuing to sleep for remaining time
328 struct timespec remaining;
329 while (nanosleep(&ts, &remaining) == -1) {
330 if (errno != EINTR) {
331 break;
332 }
333 ts = remaining;
334 }
335#endif
336}
337
338int get_pid() {
339#ifdef _WIN32
340 DWORD pid = GetCurrentProcessId();
341 // Validate that PID fits in int (should always be true)
342 if (pid > INT_MAX) {
343 return -1; // Indicate error
344 }
345 return (int)pid;
346#else // POSIX
347 return (int)getpid();
348#endif
349}
350
351unsigned long get_tid() {
352#ifdef _WIN32
353 return (unsigned long)GetCurrentThreadId();
354#else // POSIX
355 // Note: pthread_t is an opaque type and may not be an integer.
356 // Casting to unsigned long is not portable but commonly works.
357 // For true portability, consider using gettid() on Linux or
358 // platform-specific APIs to get numeric thread IDs.
359 return (unsigned long)pthread_self();
360#endif
361}
362
363long get_ncpus() {
364#ifdef _WIN32
365 SYSTEM_INFO sysinfo;
366 GetSystemInfo(&sysinfo);
367 DWORD count = sysinfo.dwNumberOfProcessors;
368
369 // Validate that the count fits in a long and is reasonable
370 if (count == 0 || count > LONG_MAX) {
371 return -1; // Indicate error
372 }
373 return (long)count;
374
375#else // POSIX
376 long ncpus = sysconf(_SC_NPROCESSORS_ONLN);
377 // sysconf returns -1 on error, pass it through
378 return ncpus;
379#endif
380}
381
382/* Platform-specific System Information Functions */
383
384#ifdef _WIN32
385
390int get_ppid() {
391 HANDLE snapshot;
392 PROCESSENTRY32 pe32;
393 DWORD current_pid = GetCurrentProcessId();
394 DWORD parent_pid = (DWORD)-1;
395
396 snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
397 if (snapshot == INVALID_HANDLE_VALUE) {
398 return -1;
399 }
400
401 pe32.dwSize = sizeof(PROCESSENTRY32);
402 if (!Process32First(snapshot, &pe32)) {
403 CloseHandle(snapshot);
404 return -1;
405 }
406
407 do {
408 if (pe32.th32ProcessID == current_pid) {
409 parent_pid = pe32.th32ParentProcessID;
410 break;
411 }
412 } while (Process32Next(snapshot, &pe32));
413
414 CloseHandle(snapshot);
415
416 if (parent_pid == (DWORD)-1 || parent_pid > INT_MAX) {
417 return -1;
418 }
419 return (int)parent_pid;
420}
421
427unsigned int get_uid() {
428 HANDLE token = NULL;
429 DWORD token_info_length = 0;
430 TOKEN_USER* token_user = NULL;
431 unsigned int uid_hash = (unsigned int)-1;
432
433 // Open current process token
434 if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token)) {
435 return (unsigned int)-1;
436 }
437
438 // Get required buffer size
439 GetTokenInformation(token, TokenUser, NULL, 0, &token_info_length);
440 if (token_info_length == 0) {
441 CloseHandle(token);
442 return (unsigned int)-1;
443 }
444
445 token_user = (TOKEN_USER*)malloc(token_info_length);
446 if (token_user == NULL) {
447 CloseHandle(token);
448 return (unsigned int)-1;
449 }
450
451 // Get user SID
452 if (!GetTokenInformation(token, TokenUser, token_user, token_info_length, &token_info_length)) {
453 free(token_user);
454 CloseHandle(token);
455 return (unsigned int)-1;
456 }
457
458 // Create a simple hash of the SID for a numeric identifier
459 PSID sid = token_user->User.Sid;
460 if (IsValidSid(sid)) {
461 DWORD sid_length = GetLengthSid(sid);
462 BYTE* sid_bytes = (BYTE*)sid;
463
464 // Simple FNV-1a hash
465 uid_hash = 2166136261u;
466 for (DWORD i = 0; i < sid_length; i++) {
467 uid_hash ^= sid_bytes[i];
468 uid_hash *= 16777619u;
469 }
470 }
471
472 free(token_user);
473 CloseHandle(token);
474 return uid_hash;
475}
476
482unsigned int get_gid() {
483 HANDLE token = NULL;
484 DWORD token_info_length = 0;
485 TOKEN_PRIMARY_GROUP* primary_group = NULL;
486 unsigned int gid_hash = (unsigned int)-1;
487
488 // Open current process token
489 if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token)) {
490 return (unsigned int)-1;
491 }
492
493 // Get required buffer size
494 GetTokenInformation(token, TokenPrimaryGroup, NULL, 0, &token_info_length);
495 if (token_info_length == 0) {
496 CloseHandle(token);
497 return (unsigned int)-1;
498 }
499
500 primary_group = (TOKEN_PRIMARY_GROUP*)malloc(token_info_length);
501 if (primary_group == NULL) {
502 CloseHandle(token);
503 return (unsigned int)-1;
504 }
505
506 // Get primary group SID
507 if (!GetTokenInformation(token, TokenPrimaryGroup, primary_group, token_info_length, &token_info_length)) {
508 free(primary_group);
509 CloseHandle(token);
510 return (unsigned int)-1;
511 }
512
513 // Create a simple hash of the SID
514 PSID sid = primary_group->PrimaryGroup;
515 if (IsValidSid(sid)) {
516 DWORD sid_length = GetLengthSid(sid);
517 BYTE* sid_bytes = (BYTE*)sid;
518
519 // Simple FNV-1a hash
520 gid_hash = 2166136261u;
521 for (DWORD i = 0; i < sid_length; i++) {
522 gid_hash ^= sid_bytes[i];
523 gid_hash *= 16777619u;
524 }
525 }
526
527 free(primary_group);
528 CloseHandle(token);
529 return gid_hash;
530}
531
533static char win_username_buffer[UNLEN + 1];
534
540char* get_username() {
541 DWORD username_len = UNLEN + 1;
542
543 if (!GetUserNameA(win_username_buffer, &username_len)) {
544 return NULL;
545 }
546
547 return win_username_buffer;
548}
549
551static char win_groupname_buffer[256];
552
559char* get_groupname() {
560 HANDLE token = NULL;
561 DWORD token_info_length = 0;
562 TOKEN_PRIMARY_GROUP* primary_group = NULL;
563 char* result = NULL;
564
565 // Open current process token
566 if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token)) {
567 return NULL;
568 }
569
570 // Get required buffer size for primary group
571 GetTokenInformation(token, TokenPrimaryGroup, NULL, 0, &token_info_length);
572 if (token_info_length == 0) {
573 CloseHandle(token);
574 return NULL;
575 }
576
577 primary_group = (TOKEN_PRIMARY_GROUP*)malloc(token_info_length);
578 if (primary_group == NULL) {
579 CloseHandle(token);
580 return NULL;
581 }
582
583 // Get primary group SID
584 if (!GetTokenInformation(token, TokenPrimaryGroup, primary_group, token_info_length, &token_info_length)) {
585 free(primary_group);
586 CloseHandle(token);
587 return NULL;
588 }
589
590 // Lookup account name from SID
591 DWORD name_len = sizeof(win_groupname_buffer);
592 DWORD domain_len = 0;
593 SID_NAME_USE sid_type;
594
595 // First call to get domain length
596 LookupAccountSidA(NULL, primary_group->PrimaryGroup, win_groupname_buffer, &name_len, NULL, &domain_len, &sid_type);
597
598 if (domain_len > 0) {
599 char* domain_buffer = (char*)malloc(domain_len);
600 if (domain_buffer != NULL) {
601 name_len = sizeof(win_groupname_buffer);
602 if (LookupAccountSidA(NULL, primary_group->PrimaryGroup, win_groupname_buffer, &name_len, domain_buffer,
603 &domain_len, &sid_type)) {
604 result = win_groupname_buffer;
605 }
606 free(domain_buffer);
607 }
608 }
609
610 free(primary_group);
611 CloseHandle(token);
612 return result;
613}
614
615#else // POSIX
616
617int get_ppid() {
618 pid_t ppid = getppid();
619 // POSIX guarantees ppid fits in int, but be defensive
620 if (ppid < 0 || ppid > INT_MAX) {
621 return -1;
622 }
623 return (int)ppid;
624}
625
626unsigned int get_uid() { return (unsigned int)getuid(); }
627
628unsigned int get_gid() { return (unsigned int)getgid(); }
629
631 struct passwd* pw = getpwuid(getuid());
632 if (pw == NULL) {
633 return NULL; // Don't call perror - not thread-safe
634 }
635 // Return pointer to static data - documented as not thread-safe
636 return pw->pw_name;
637}
638
640 struct group* gr = getgrgid(getgid());
641 if (gr == NULL) {
642 return NULL; // Don't call perror - not thread-safe
643 }
644
645 // Return pointer to static data - documented as not thread-safe
646 return gr->gr_name;
647}
648
649#endif // _WIN32
A modern, cross-platform API for process management and IPC.
unsigned int get_uid()
Definition thread.c:626
unsigned int get_gid()
Definition thread.c:628
int get_ppid()
Definition thread.c:617
void thread_exit(void *retval)
Definition thread.c:231
char * get_groupname()
Definition thread.c:639
int thread_create(Thread *thread, ThreadStartRoutine start_routine, void *data)
Definition thread.c:95
pthread_attr_t ThreadAttr
Definition thread.h:81
int thread_attr_destroy(ThreadAttr *attr)
Definition thread.c:292
void sleep_ms(int ms)
Definition thread.c:310
int thread_create_attr(Thread *thread, ThreadAttr *attr, ThreadStartRoutine start_routine, void *data)
Definition thread.c:140
pthread_t thread_self()
Definition thread.c:267
int get_pid()
Definition thread.c:338
unsigned long get_tid()
Definition thread.c:351
int thread_detach(Thread tid)
Definition thread.c:240
int thread_join(Thread tid, void **retval)
Definition thread.c:184
pthread_t Thread
Definition thread.h:78
void *(* ThreadStartRoutine)(void *arg)
Definition thread.h:92
long get_ncpus()
Definition thread.c:363
int thread_attr_init(ThreadAttr *attr)
Definition thread.c:272
char * get_username()
Definition thread.c:630