fix: prevent segfault from rapid module writes
Add platform-specific protections against race conditions when module files are written rapidly (compilation, copying, etc). Linux (inotify): - Deduplicate events within a poll cycle to prevent double-unload - When duplicate RELOAD events occur, earlier events are skipped and only the final event is processed BSD (kqueue): - Add file readiness check with flock() before processing mtime changes - Files that aren't ready have their mtime reset, skipping the reload All Unix platforms: - Use atomic .tmp + rename for module copying to prevent loading partially-written files Writes that occur during an active reload or when files aren't ready are skipped. A subsequent write is required to trigger detection of those changes. Fixes segfault caused by processing duplicate reload events (Linux) or copying incomplete files mid-write (BSD).
This commit is contained in:
+176
-94
@@ -4,6 +4,10 @@
|
|||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
|
#ifndef _WIN32
|
||||||
|
#include <sys/file.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
#if defined(__linux__)
|
#if defined(__linux__)
|
||||||
#include <dirent.h>
|
#include <dirent.h>
|
||||||
#include <dlfcn.h>
|
#include <dlfcn.h>
|
||||||
@@ -28,10 +32,11 @@ int is_mod_loaded(const char *module_name);
|
|||||||
uint8_t is_valid_module_file(const char *filename);
|
uint8_t is_valid_module_file(const char *filename);
|
||||||
void extract_module_id(const char *path, char *out_id);
|
void extract_module_id(const char *path, char *out_id);
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifndef __linux__
|
||||||
static uint8_t is_file_ready(const char *dir_path, const char *filename)
|
static uint8_t is_file_ready(const char *dir_path, const char *filename)
|
||||||
{
|
{
|
||||||
char full_path[STK_PATH_MAX_OS];
|
char full_path[STK_PATH_MAX_OS];
|
||||||
|
#ifdef _WIN32
|
||||||
DWORD size;
|
DWORD size;
|
||||||
HANDLE h;
|
HANDLE h;
|
||||||
|
|
||||||
@@ -48,10 +53,33 @@ static uint8_t is_file_ready(const char *dir_path, const char *filename)
|
|||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
|
#else
|
||||||
|
int fd;
|
||||||
|
struct stat st;
|
||||||
|
|
||||||
|
sprintf(full_path, "%s/%s", dir_path, filename);
|
||||||
|
|
||||||
|
if (stat(full_path, &st) != 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if (st.st_size < 1024)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
fd = open(full_path, O_RDONLY);
|
||||||
|
if (fd < 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if (flock(fd, LOCK_EX | LOCK_NB) != 0) {
|
||||||
|
close(fd);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
flock(fd, LOCK_UN);
|
||||||
|
close(fd);
|
||||||
|
return 1;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifndef __linux__
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
char filename[STK_PATH_MAX];
|
char filename[STK_PATH_MAX];
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
@@ -116,26 +144,62 @@ uint8_t platform_copy_file(const char *from, const char *to)
|
|||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
FILE *src = NULL, *dst = NULL;
|
FILE *src = NULL, *dst = NULL;
|
||||||
|
char tmp_path[STK_PATH_MAX_OS];
|
||||||
size_t n;
|
size_t n;
|
||||||
|
#ifndef __linux__
|
||||||
|
char dir_path[STK_PATH_MAX_OS];
|
||||||
|
const char *filename;
|
||||||
|
|
||||||
|
filename = strrchr(from, '/');
|
||||||
|
if (filename) {
|
||||||
|
size_t dir_len = filename - from;
|
||||||
|
strncpy(dir_path, from, dir_len);
|
||||||
|
dir_path[dir_len] = '\0';
|
||||||
|
filename++;
|
||||||
|
} else {
|
||||||
|
strcpy(dir_path, ".");
|
||||||
|
filename = from;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!is_file_ready(dir_path, filename))
|
||||||
|
goto done;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
sprintf(tmp_path, "%s.tmp", to);
|
||||||
|
|
||||||
src = fopen(from, "rb");
|
src = fopen(from, "rb");
|
||||||
if (!src)
|
if (!src)
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
|
|
||||||
dst = fopen(to, "wb");
|
dst = fopen(tmp_path, "wb");
|
||||||
if (!dst)
|
if (!dst)
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
|
|
||||||
while ((n = fread(buf, 1, sizeof(buf), src)) > 0)
|
while ((n = fread(buf, 1, sizeof(buf), src)) > 0)
|
||||||
fwrite(buf, 1, n, dst);
|
fwrite(buf, 1, n, dst);
|
||||||
|
|
||||||
ret = STK_PLATFORM_OPERATION_SUCCESS;
|
if (src)
|
||||||
|
fclose(src);
|
||||||
|
if (dst)
|
||||||
|
fclose(dst);
|
||||||
|
src = NULL;
|
||||||
|
dst = NULL;
|
||||||
|
|
||||||
|
if (rename(tmp_path, to) == 0)
|
||||||
|
ret = STK_PLATFORM_OPERATION_SUCCESS;
|
||||||
|
else
|
||||||
|
unlink(tmp_path);
|
||||||
|
|
||||||
|
goto done;
|
||||||
|
|
||||||
cleanup:
|
cleanup:
|
||||||
if (src)
|
if (src)
|
||||||
fclose(src);
|
fclose(src);
|
||||||
if (dst)
|
if (dst)
|
||||||
fclose(dst);
|
fclose(dst);
|
||||||
|
unlink(tmp_path);
|
||||||
|
|
||||||
|
done:
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
@@ -222,115 +286,116 @@ void *platform_get_symbol(void *h, const char *s)
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
char (*platform_directory_init_scan(const char *dir_path, size_t *out_count))
|
char (*platform_directory_init_scan(const char *dir_path,
|
||||||
[STK_PATH_MAX] {
|
size_t *out_count))[STK_PATH_MAX]
|
||||||
size_t count = 0, i = 0;
|
{
|
||||||
char (*list)[STK_PATH_MAX] = NULL;
|
size_t count = 0, i = 0;
|
||||||
|
char(*list)[STK_PATH_MAX] = NULL;
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
WIN32_FIND_DATAA fd;
|
WIN32_FIND_DATAA fd;
|
||||||
HANDLE h;
|
HANDLE h;
|
||||||
char s[STK_PATH_MAX_OS];
|
char s[STK_PATH_MAX_OS];
|
||||||
|
|
||||||
sprintf(s, "%s\\*", dir_path);
|
sprintf(s, "%s\\*", dir_path);
|
||||||
h = FindFirstFileA(s, &fd);
|
h = FindFirstFileA(s, &fd);
|
||||||
if (h == INVALID_HANDLE_VALUE)
|
if (h == INVALID_HANDLE_VALUE)
|
||||||
goto create_and_exit;
|
goto create_and_exit;
|
||||||
|
|
||||||
do {
|
do {
|
||||||
if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
|
if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
|
||||||
continue;
|
continue;
|
||||||
if (is_valid_module_file(fd.cFileName))
|
if (is_valid_module_file(fd.cFileName))
|
||||||
count++;
|
count++;
|
||||||
} while (FindNextFileA(h, &fd));
|
} while (FindNextFileA(h, &fd));
|
||||||
|
|
||||||
FindClose(h);
|
FindClose(h);
|
||||||
|
|
||||||
if (count == 0)
|
if (count == 0)
|
||||||
goto exit;
|
goto exit;
|
||||||
|
|
||||||
list = malloc(count * sizeof(*list));
|
list = malloc(count * sizeof(*list));
|
||||||
if (!list)
|
if (!list)
|
||||||
goto exit;
|
goto exit;
|
||||||
|
|
||||||
h = FindFirstFileA(s, &fd);
|
h = FindFirstFileA(s, &fd);
|
||||||
if (h == INVALID_HANDLE_VALUE)
|
if (h == INVALID_HANDLE_VALUE)
|
||||||
goto exit;
|
goto exit;
|
||||||
|
|
||||||
do {
|
do {
|
||||||
if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
|
if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
|
||||||
continue;
|
continue;
|
||||||
if (is_valid_module_file(fd.cFileName) && i < count)
|
if (is_valid_module_file(fd.cFileName) && i < count)
|
||||||
strncpy(list[i++], fd.cFileName, STK_PATH_MAX - 1);
|
strncpy(list[i++], fd.cFileName, STK_PATH_MAX - 1);
|
||||||
} while (FindNextFileA(h, &fd));
|
} while (FindNextFileA(h, &fd));
|
||||||
|
|
||||||
FindClose(h);
|
FindClose(h);
|
||||||
goto exit;
|
goto exit;
|
||||||
|
|
||||||
create_and_exit:
|
create_and_exit:
|
||||||
platform_mkdir(dir_path);
|
platform_mkdir(dir_path);
|
||||||
exit:
|
exit:
|
||||||
*out_count = i;
|
*out_count = i;
|
||||||
return list;
|
return list;
|
||||||
#else
|
#else
|
||||||
DIR *d;
|
DIR *d;
|
||||||
struct dirent *e;
|
struct dirent *e;
|
||||||
struct stat st;
|
struct stat st;
|
||||||
char f[STK_PATH_MAX_OS];
|
char f[STK_PATH_MAX_OS];
|
||||||
|
|
||||||
d = opendir(dir_path);
|
d = opendir(dir_path);
|
||||||
if (!d)
|
if (!d)
|
||||||
goto create_and_exit;
|
goto create_and_exit;
|
||||||
|
|
||||||
count_loop:
|
count_loop:
|
||||||
e = readdir(d);
|
e = readdir(d);
|
||||||
if (!e)
|
if (!e)
|
||||||
goto count_done;
|
goto count_done;
|
||||||
|
|
||||||
sprintf(f, "%s/%s", dir_path, e->d_name);
|
sprintf(f, "%s/%s", dir_path, e->d_name);
|
||||||
if (!is_valid_module_file(e->d_name))
|
if (!is_valid_module_file(e->d_name))
|
||||||
goto count_loop;
|
goto count_loop;
|
||||||
|
|
||||||
if (stat(f, &st) != 0 || !S_ISREG(st.st_mode))
|
if (stat(f, &st) != 0 || !S_ISREG(st.st_mode))
|
||||||
goto count_loop;
|
goto count_loop;
|
||||||
|
|
||||||
count++;
|
count++;
|
||||||
goto count_loop;
|
goto count_loop;
|
||||||
|
|
||||||
count_done:
|
count_done:
|
||||||
if (count == 0)
|
if (count == 0)
|
||||||
goto close_and_exit;
|
goto close_and_exit;
|
||||||
|
|
||||||
rewinddir(d);
|
rewinddir(d);
|
||||||
list = malloc(count * sizeof(*list));
|
list = malloc(count * sizeof(*list));
|
||||||
if (!list)
|
if (!list)
|
||||||
goto close_and_exit;
|
goto close_and_exit;
|
||||||
|
|
||||||
fill_loop:
|
fill_loop:
|
||||||
e = readdir(d);
|
e = readdir(d);
|
||||||
if (!e || i >= count)
|
if (!e || i >= count)
|
||||||
goto close_and_exit;
|
goto close_and_exit;
|
||||||
|
|
||||||
sprintf(f, "%s/%s", dir_path, e->d_name);
|
sprintf(f, "%s/%s", dir_path, e->d_name);
|
||||||
if (!is_valid_module_file(e->d_name))
|
if (!is_valid_module_file(e->d_name))
|
||||||
goto fill_loop;
|
goto fill_loop;
|
||||||
|
|
||||||
if (stat(f, &st) != 0 || !S_ISREG(st.st_mode))
|
if (stat(f, &st) != 0 || !S_ISREG(st.st_mode))
|
||||||
goto fill_loop;
|
goto fill_loop;
|
||||||
|
|
||||||
strncpy(list[i++], e->d_name, STK_PATH_MAX - 1);
|
strncpy(list[i++], e->d_name, STK_PATH_MAX - 1);
|
||||||
goto fill_loop;
|
goto fill_loop;
|
||||||
|
|
||||||
create_and_exit:
|
create_and_exit:
|
||||||
platform_mkdir(dir_path);
|
platform_mkdir(dir_path);
|
||||||
*out_count = 0;
|
*out_count = 0;
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
close_and_exit:
|
close_and_exit:
|
||||||
closedir(d);
|
closedir(d);
|
||||||
*out_count = i;
|
*out_count = i;
|
||||||
return list;
|
return list;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
#if !defined(__linux__) && !defined(_WIN32)
|
#if !defined(__linux__) && !defined(_WIN32)
|
||||||
static void update_watches(platform_watch_context_t *ctx)
|
static void update_watches(platform_watch_context_t *ctx)
|
||||||
@@ -585,7 +650,7 @@ stk_module_event_t *platform_directory_watch_check(
|
|||||||
int fd = (int)(long)handle;
|
int fd = (int)(long)handle;
|
||||||
char buf[STK_EVENT_BUFFER];
|
char buf[STK_EVENT_BUFFER];
|
||||||
ssize_t len;
|
ssize_t len;
|
||||||
size_t index = 0, count = 0;
|
size_t index = 0, count = 0, i;
|
||||||
stk_module_event_t *evs;
|
stk_module_event_t *evs;
|
||||||
char *ptr, *end;
|
char *ptr, *end;
|
||||||
struct inotify_event *e;
|
struct inotify_event *e;
|
||||||
@@ -660,6 +725,7 @@ stk_module_event_t *platform_directory_watch_check(
|
|||||||
if (e->mask & (IN_CLOSE_WRITE | IN_MOVED_TO)) {
|
if (e->mask & (IN_CLOSE_WRITE | IN_MOVED_TO)) {
|
||||||
char event_module_name[STK_MOD_ID_BUFFER];
|
char event_module_name[STK_MOD_ID_BUFFER];
|
||||||
extract_module_id(e->name, event_module_name);
|
extract_module_id(e->name, event_module_name);
|
||||||
|
|
||||||
if (is_mod_loaded(event_module_name) >= 0)
|
if (is_mod_loaded(event_module_name) >= 0)
|
||||||
event_type = STK_MOD_RELOAD;
|
event_type = STK_MOD_RELOAD;
|
||||||
else
|
else
|
||||||
@@ -672,6 +738,16 @@ stk_module_event_t *platform_directory_watch_check(
|
|||||||
ptr += sizeof(struct inotify_event) + e->len;
|
ptr += sizeof(struct inotify_event) + e->len;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < index; ++i) {
|
||||||
|
size_t j;
|
||||||
|
for (j = i + 1; j < index; ++j) {
|
||||||
|
if (strcmp((*file_list)[i], (*file_list)[j]) == 0) {
|
||||||
|
evs[i] = -1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
*out_count = index;
|
*out_count = index;
|
||||||
return evs;
|
return evs;
|
||||||
|
|
||||||
@@ -822,10 +898,16 @@ build_diff:
|
|||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
if (ctx->snaps[i].mtime != new_snaps[j].mtime) {
|
if (ctx->snaps[i].mtime != new_snaps[j].mtime) {
|
||||||
strncpy((*file_list)[ev_index],
|
if (is_file_ready(ctx->path,
|
||||||
new_snaps[j].filename,
|
new_snaps[j].filename)) {
|
||||||
STK_PATH_MAX - 1);
|
strncpy((*file_list)[ev_index],
|
||||||
evs[ev_index++] = STK_MOD_RELOAD;
|
new_snaps[j].filename,
|
||||||
|
STK_PATH_MAX - 1);
|
||||||
|
evs[ev_index++] = STK_MOD_RELOAD;
|
||||||
|
} else {
|
||||||
|
new_snaps[j].mtime =
|
||||||
|
ctx->snaps[i].mtime;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
break;
|
break;
|
||||||
|
|||||||
Reference in New Issue
Block a user