mirror of
https://github.com/pi-hole/FTL.git
synced 2024-10-26 16:52:18 +02:00
f518fa1c08
It currently contains the version of shared memory that FTL is exposing. The current version of shared memory is 1. Whenever a change is made to structures stored in shared memory, or the layout of shared memory, the version should be incremented (like how the database version is incremented when the database changes). This version number will be used by the API to verify it is using the same version of shared memory as FTL. Signed-off-by: Mcat12 <newtoncat12@yahoo.com>
471 lines
15 KiB
C
471 lines
15 KiB
C
/* Pi-hole: A black hole for Internet advertisements
|
|
* (c) 2018 Pi-hole, LLC (https://pi-hole.net)
|
|
* Network-wide ad blocking via your own hardware.
|
|
*
|
|
* FTL Engine
|
|
* Shared memory subroutines
|
|
*
|
|
* This file is copyright under the latest version of the EUPL.
|
|
* Please see LICENSE file for your rights under this license. */
|
|
|
|
#include "FTL.h"
|
|
#include "shmem.h"
|
|
|
|
/// The version of shared memory used
|
|
#define SHARED_MEMORY_VERSION 1
|
|
|
|
/// The name of the shared memory. Use this when connecting to the shared memory.
|
|
#define SHARED_LOCK_NAME "/FTL-lock"
|
|
#define SHARED_STRINGS_NAME "/FTL-strings"
|
|
#define SHARED_COUNTERS_NAME "/FTL-counters"
|
|
#define SHARED_DOMAINS_NAME "/FTL-domains"
|
|
#define SHARED_CLIENTS_NAME "/FTL-clients"
|
|
#define SHARED_QUERIES_NAME "/FTL-queries"
|
|
#define SHARED_FORWARDED_NAME "/FTL-forwarded"
|
|
#define SHARED_OVERTIME_NAME "/FTL-overTime"
|
|
#define SHARED_SETTINGS_NAME "/FTL-settings"
|
|
#define SHARED_OVERTIMECLIENT_PREFIX "/FTL-client-"
|
|
|
|
/// The pointer in shared memory to the shared string buffer
|
|
static SharedMemory shm_lock = { 0 };
|
|
static SharedMemory shm_strings = { 0 };
|
|
static SharedMemory shm_counters = { 0 };
|
|
static SharedMemory shm_domains = { 0 };
|
|
static SharedMemory shm_clients = { 0 };
|
|
static SharedMemory shm_queries = { 0 };
|
|
static SharedMemory shm_forwarded = { 0 };
|
|
static SharedMemory shm_overTime = { 0 };
|
|
static SharedMemory shm_settings = { 0 };
|
|
|
|
static SharedMemory *shm_overTimeClients = NULL;
|
|
static int overTimeClientCount = 0;
|
|
|
|
typedef struct {
|
|
pthread_mutex_t lock;
|
|
bool waitingForLock;
|
|
} ShmLock;
|
|
static ShmLock *shmLock = NULL;
|
|
|
|
static int pagesize;
|
|
static unsigned int next_pos = 0;
|
|
|
|
unsigned long long addstr(const char *str)
|
|
{
|
|
if(str == NULL)
|
|
{
|
|
logg("WARN: Called addstr() with NULL pointer");
|
|
return 0;
|
|
}
|
|
|
|
// Get string length
|
|
size_t len = strlen(str);
|
|
|
|
// If this is an empty string, use the one at position zero
|
|
if(len == 0) {
|
|
return 0;
|
|
}
|
|
|
|
if(debug) logg("Adding \"%s\" (len %i) to buffer. next_pos is %i", str, len, next_pos);
|
|
|
|
// Reserve additional memory if necessary
|
|
size_t required_size = next_pos + len + 1;
|
|
// Need to cast to long long because size_t calculations cannot be negative
|
|
if((long long)required_size-(long long)shm_strings.size > 0 &&
|
|
!realloc_shm(&shm_strings, shm_strings.size + pagesize))
|
|
return 0;
|
|
|
|
// Copy the C string pointed by str into the shared string buffer
|
|
strncpy(&((char*)shm_strings.ptr)[next_pos], str, len);
|
|
((char*)shm_strings.ptr)[next_pos + len] = '\0';
|
|
|
|
// Increment string length counter
|
|
next_pos += len+1;
|
|
|
|
// Return start of stored string
|
|
return (next_pos - (len + 1));
|
|
}
|
|
|
|
char *getstr(unsigned long long pos)
|
|
{
|
|
return &((char*)shm_strings.ptr)[pos];
|
|
}
|
|
|
|
static char *clientShmName(int id) {
|
|
int name_len = 1 + snprintf(NULL, 0, "%s%d", SHARED_OVERTIMECLIENT_PREFIX, id);
|
|
char *name = malloc(sizeof(char) * name_len);
|
|
snprintf(name, (size_t) name_len, "%s%d", SHARED_OVERTIMECLIENT_PREFIX, id);
|
|
|
|
return name;
|
|
}
|
|
|
|
void newOverTimeClient() {
|
|
// Get the name of the new shared memory.
|
|
// This will be used in the struct, so it should not be immediately freed.
|
|
char *name = clientShmName(overTimeClientCount);
|
|
|
|
// Create the shared memory with enough space for the current overTime slots
|
|
shm_unlink(name);
|
|
SharedMemory shm = create_shm(name, (counters->overTime/pagesize + 1)*pagesize*sizeof(int));
|
|
if(shm.ptr == NULL) {
|
|
free(shm.name);
|
|
logg("Failed to initialize new overTime client %d", overTimeClientCount);
|
|
return;
|
|
}
|
|
|
|
// Make space for the new shared memory
|
|
shm_overTimeClients = realloc(shm_overTimeClients, sizeof(SharedMemory) * (overTimeClientCount + 1));
|
|
overTimeClientCount++;
|
|
shm_overTimeClients[overTimeClientCount-1] = shm;
|
|
|
|
// Add to overTimeClientData
|
|
overTimeClientData = realloc(overTimeClientData, sizeof(int*) * (overTimeClientCount));
|
|
overTimeClientData[overTimeClientCount-1] = shm.ptr;
|
|
}
|
|
|
|
void addOverTimeClientSlot() {
|
|
// For each client slot, add pagesize overTime slots
|
|
for(int i = 0; i < overTimeClientCount; i++)
|
|
{
|
|
// Only increase the size of the shm object if needed
|
|
// shm_overTimeClients[i].size stores the size of the memory in bytes whereas
|
|
// counters->overTime (effectively) stores the number of slots each overTime
|
|
// client should have. Hence, counters->overTime needs to be multiplied by
|
|
// sizeof(int) to get the actual requested memory size
|
|
if(shm_overTimeClients[i].size > (size_t)counters->overTime*sizeof(int))
|
|
continue;
|
|
|
|
// Reallocate with one more slot
|
|
realloc_shm(&shm_overTimeClients[i], (counters->overTime + pagesize)*sizeof(int));
|
|
|
|
// Update overTimeClientData
|
|
overTimeClientData[i] = shm_overTimeClients[i].ptr;
|
|
}
|
|
}
|
|
|
|
/// Create a mutex for shared memory
|
|
pthread_mutex_t create_mutex() {
|
|
pthread_mutexattr_t lock_attr = {};
|
|
pthread_mutex_t lock = {};
|
|
|
|
// Initialize the lock attributes
|
|
pthread_mutexattr_init(&lock_attr);
|
|
|
|
// Allow the lock to be used by other processes
|
|
pthread_mutexattr_setpshared(&lock_attr, PTHREAD_PROCESS_SHARED);
|
|
|
|
// Make the lock robust against process death
|
|
pthread_mutexattr_setrobust(&lock_attr, PTHREAD_MUTEX_ROBUST);
|
|
|
|
// Initialize the lock
|
|
pthread_mutex_init(&lock, &lock_attr);
|
|
|
|
// Destroy the lock attributes since we're done with it
|
|
pthread_mutexattr_destroy(&lock_attr);
|
|
|
|
return lock;
|
|
}
|
|
|
|
void _lock_shm(const char* function, const int line, const char * file) {
|
|
// Signal that FTL is waiting for a lock
|
|
shmLock->waitingForLock = true;
|
|
|
|
if(debug) logg("Waiting for lock in %s() (%s:%i)", function, file, line);
|
|
|
|
int result = pthread_mutex_lock(&shmLock->lock);
|
|
|
|
if(debug) logg("Obtained lock for %s() (%s:%i)", function, file, line);
|
|
|
|
// Turn off the waiting for lock signal to notify everyone who was
|
|
// deferring to FTL that they can jump in the lock queue.
|
|
shmLock->waitingForLock = false;
|
|
|
|
if(result == EOWNERDEAD) {
|
|
// Try to make the lock consistent if the other process died while
|
|
// holding the lock
|
|
result = pthread_mutex_consistent(&shmLock->lock);
|
|
}
|
|
|
|
if(result != 0)
|
|
logg("Failed to obtain SHM lock: %s", strerror(result));
|
|
}
|
|
|
|
void _unlock_shm(const char* function, const int line, const char * file) {
|
|
int result = pthread_mutex_unlock(&shmLock->lock);
|
|
|
|
if(debug) logg("Removed lock in %s() (%s:%i)", function, file, line);
|
|
|
|
if(result != 0)
|
|
logg("Failed to unlock SHM lock: %s", strerror(result));
|
|
}
|
|
|
|
bool init_shmem(void)
|
|
{
|
|
// Get kernel's page size
|
|
pagesize = getpagesize();
|
|
|
|
/****************************** shared memory lock ******************************/
|
|
// Try to create shared memory object
|
|
shm_lock = create_shm(SHARED_LOCK_NAME, sizeof(ShmLock));
|
|
if(shm_lock.ptr == NULL)
|
|
return false;
|
|
shmLock = (ShmLock*) shm_lock.ptr;
|
|
shmLock->lock = create_mutex();
|
|
shmLock->waitingForLock = false;
|
|
|
|
/****************************** shared strings buffer ******************************/
|
|
// Try to create shared memory object
|
|
shm_strings = create_shm(SHARED_STRINGS_NAME, pagesize);
|
|
if(shm_strings.ptr == NULL)
|
|
return false;
|
|
|
|
// Initialize shared string object with an empty string at position zero
|
|
((char*)shm_strings.ptr)[0] = '\0';
|
|
next_pos = 1;
|
|
|
|
/****************************** shared counters struct ******************************/
|
|
// Try to create shared memory object
|
|
shm_counters = create_shm(SHARED_COUNTERS_NAME, sizeof(countersStruct));
|
|
if(shm_counters.ptr == NULL)
|
|
return false;
|
|
counters = (countersStruct*)shm_counters.ptr;
|
|
|
|
/****************************** shared domains struct ******************************/
|
|
// Try to create shared memory object
|
|
shm_domains = create_shm(SHARED_DOMAINS_NAME, pagesize*sizeof(domainsDataStruct));
|
|
if(shm_domains.ptr == NULL)
|
|
return false;
|
|
domains = (domainsDataStruct*)shm_domains.ptr;
|
|
counters->domains_MAX = pagesize;
|
|
|
|
/****************************** shared clients struct ******************************/
|
|
// Try to create shared memory object
|
|
shm_clients = create_shm(SHARED_CLIENTS_NAME, pagesize*sizeof(clientsDataStruct));
|
|
if(shm_clients.ptr == NULL)
|
|
return false;
|
|
clients = (clientsDataStruct*)shm_clients.ptr;
|
|
counters->clients_MAX = pagesize;
|
|
|
|
/****************************** shared forwarded struct ******************************/
|
|
// Try to create shared memory object
|
|
shm_forwarded = create_shm(SHARED_FORWARDED_NAME, pagesize*sizeof(forwardedDataStruct));
|
|
if(shm_forwarded.ptr == NULL)
|
|
return false;
|
|
forwarded = (forwardedDataStruct*)shm_forwarded.ptr;
|
|
counters->forwarded_MAX = pagesize;
|
|
|
|
/****************************** shared queries struct ******************************/
|
|
// Try to create shared memory object
|
|
shm_queries = create_shm(SHARED_QUERIES_NAME, pagesize*sizeof(queriesDataStruct));
|
|
if(shm_queries.ptr == NULL)
|
|
return false;
|
|
queries = (queriesDataStruct*)shm_queries.ptr;
|
|
counters->queries_MAX = pagesize;
|
|
|
|
/****************************** shared overTime struct ******************************/
|
|
// Try to create shared memory object
|
|
shm_overTime = create_shm(SHARED_OVERTIME_NAME, pagesize*sizeof(overTimeDataStruct));
|
|
if(shm_overTime.ptr == NULL)
|
|
return false;
|
|
overTime = (overTimeDataStruct*)shm_overTime.ptr;
|
|
counters->overTime_MAX = pagesize;
|
|
|
|
/****************************** shared settings struct ******************************/
|
|
// Try to create shared memory object
|
|
shm_settings = create_shm(SHARED_SETTINGS_NAME, sizeof(ShmSettings));
|
|
if(shm_settings.ptr == NULL)
|
|
return false;
|
|
ShmSettings *settings = (ShmSettings*)shm_settings.ptr;
|
|
settings->version = SHARED_MEMORY_VERSION;
|
|
|
|
return true;
|
|
}
|
|
|
|
void destroy_shmem(void)
|
|
{
|
|
pthread_mutex_destroy(&shmLock->lock);
|
|
shmLock = NULL;
|
|
|
|
delete_shm(&shm_lock);
|
|
delete_shm(&shm_strings);
|
|
delete_shm(&shm_counters);
|
|
delete_shm(&shm_domains);
|
|
delete_shm(&shm_clients);
|
|
delete_shm(&shm_queries);
|
|
delete_shm(&shm_forwarded);
|
|
delete_shm(&shm_overTime);
|
|
delete_shm(&shm_settings);
|
|
|
|
for(int i = 0; i < overTimeClientCount; i++) {
|
|
delete_shm(&shm_overTimeClients[i]);
|
|
free(shm_overTimeClients[i].name);
|
|
}
|
|
}
|
|
|
|
SharedMemory create_shm(char *name, size_t size)
|
|
{
|
|
if(debug) logg("Creating shared memory with name \"%s\" and size %zu", name, size);
|
|
|
|
SharedMemory sharedMemory = {
|
|
.name = name,
|
|
.size = size,
|
|
.ptr = NULL
|
|
};
|
|
|
|
// Try unlinking the shared memory object before creating a new one.
|
|
// If the object is still existing, e.g., due to a past unclean exit
|
|
// of FTL, shm_open() would fail with error "File exists"
|
|
int ret = shm_unlink(name);
|
|
// Check return code. shm_unlink() returns -1 on error and sets errno
|
|
// We specifically ignore ENOENT (No such file or directory) as this is not an
|
|
// error in our use case (we only want the file to be deleted when existing)
|
|
if(ret != 0 && errno != ENOENT)
|
|
logg("create_shm(): shm_unlink(\"%s\") failed: %s (%i)", name, strerror(errno), errno);
|
|
|
|
// Create the shared memory file in read/write mode with 600 permissions
|
|
int fd = shm_open(sharedMemory.name, O_CREAT | O_EXCL | O_TRUNC | O_RDWR, S_IRUSR | S_IWUSR);
|
|
|
|
// Check for `shm_open` error
|
|
if(fd == -1)
|
|
{
|
|
logg("create_shm(): Failed to create_shm shared memory object \"%s\": %s",
|
|
name, strerror(errno));
|
|
return sharedMemory;
|
|
}
|
|
|
|
// Resize shared memory file
|
|
int result = ftruncate(fd, size);
|
|
|
|
// Check for `ftruncate` error
|
|
if(result == -1)
|
|
{
|
|
logg("create_shm(): ftruncate(%i, %zu): Failed to resize shared memory object \"%s\": %s",
|
|
fd, size, sharedMemory.name, strerror(errno));
|
|
return sharedMemory;
|
|
}
|
|
|
|
// Create shared memory mapping
|
|
void *shm = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
|
|
|
|
// Check for `mmap` error
|
|
if(shm == MAP_FAILED)
|
|
{
|
|
logg("create_shm(): Failed to map shared memory object \"%s\" (%i): %s",
|
|
sharedMemory.name, fd, strerror(errno));
|
|
return sharedMemory;
|
|
}
|
|
|
|
// Close shared memory object file descriptor as it is no longer
|
|
// needed after having called mmap()
|
|
close(fd);
|
|
|
|
sharedMemory.ptr = shm;
|
|
return sharedMemory;
|
|
}
|
|
|
|
void *enlarge_shmem_struct(char type)
|
|
{
|
|
SharedMemory *sharedMemory;
|
|
size_t sizeofobj;
|
|
int *counter;
|
|
|
|
// Select type of struct that should be enlarged
|
|
switch(type)
|
|
{
|
|
case QUERIES:
|
|
sharedMemory = &shm_queries;
|
|
sizeofobj = sizeof(queriesDataStruct);
|
|
counter = &counters->queries_MAX;
|
|
break;
|
|
case CLIENTS:
|
|
sharedMemory = &shm_clients;
|
|
sizeofobj = sizeof(clientsDataStruct);
|
|
counter = &counters->clients_MAX;
|
|
break;
|
|
case DOMAINS:
|
|
sharedMemory = &shm_domains;
|
|
sizeofobj = sizeof(domainsDataStruct);
|
|
counter = &counters->domains_MAX;
|
|
break;
|
|
case FORWARDED:
|
|
sharedMemory = &shm_forwarded;
|
|
sizeofobj = sizeof(forwardedDataStruct);
|
|
counter = &counters->forwarded_MAX;
|
|
break;
|
|
case OVERTIME:
|
|
sharedMemory = &shm_overTime;
|
|
sizeofobj = sizeof(overTimeDataStruct);
|
|
counter = &counters->overTime_MAX;
|
|
break;
|
|
default:
|
|
logg("Invalid argument in enlarge_shmem_struct(): %i", type);
|
|
return 0;
|
|
}
|
|
|
|
// Reallocate enough space for 4096 instances of requested object
|
|
realloc_shm(sharedMemory, sharedMemory->size + pagesize*sizeofobj);
|
|
|
|
// Add allocated memory to corresponding counter
|
|
*counter += pagesize;
|
|
|
|
return sharedMemory->ptr;
|
|
}
|
|
|
|
bool realloc_shm(SharedMemory *sharedMemory, size_t size) {
|
|
logg("Resizing \"%s\" from %zu to %zu", sharedMemory->name, sharedMemory->size, size);
|
|
|
|
int result = munmap(sharedMemory->ptr, sharedMemory->size);
|
|
if(result != 0)
|
|
logg("realloc_shm(): munmap(%p, %zu) failed: %s", sharedMemory->ptr, sharedMemory->size, strerror(errno));
|
|
|
|
// Open shared memory object
|
|
int fd = shm_open(sharedMemory->name, O_RDWR, S_IRUSR | S_IWUSR);
|
|
if(fd == -1)
|
|
{
|
|
logg("realloc_shm(): Failed to open shared memory object \"%s\": %s",
|
|
sharedMemory->name, strerror(errno));
|
|
return false;
|
|
}
|
|
|
|
// Resize shard memory object to requested size
|
|
result = ftruncate(fd, size);
|
|
if(result == -1) {
|
|
logg("realloc_shm(): ftruncate(%i, %zu): Failed to resize \"%s\": %s",
|
|
fd, size, sharedMemory->name, strerror(errno));
|
|
return false;
|
|
}
|
|
|
|
// void *new_ptr = mremap(sharedMemory->ptr, sharedMemory->size, size, MREMAP_MAYMOVE);
|
|
void *new_ptr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
|
|
if(new_ptr == MAP_FAILED)
|
|
{
|
|
logg("realloc_shm(): mremap(%p, %zu, %zu, MREMAP_MAYMOVE): Failed to reallocate \"%s\" (%i): %s",
|
|
sharedMemory->ptr, sharedMemory->size, size, sharedMemory->name, fd,
|
|
strerror(errno));
|
|
return false;
|
|
}
|
|
|
|
// Close shared memory object file descriptor as it is no longer
|
|
// needed after having called mmap()
|
|
close(fd);
|
|
|
|
sharedMemory->ptr = new_ptr;
|
|
sharedMemory->size = size;
|
|
|
|
return true;
|
|
}
|
|
|
|
void delete_shm(SharedMemory *sharedMemory)
|
|
{
|
|
// Unmap shared memory
|
|
int ret;
|
|
ret = munmap(sharedMemory->ptr, sharedMemory->size);
|
|
if(ret != 0)
|
|
logg("delete_shm(): munmap(%p, %zu) failed: %s", sharedMemory->ptr, sharedMemory->size, strerror(errno));
|
|
|
|
// Now you can no longer `shm_open` the memory,
|
|
// and once all others unlink, it will be destroyed.
|
|
ret = shm_unlink(sharedMemory->name);
|
|
if(ret != 0)
|
|
logg("delete_shm(): shm_unlink(%s) failed: %s", sharedMemory->name, strerror(errno));
|
|
}
|