/* ** Copyright (C) 2001-2025 Zabbix SIA ** ** This program is free software: you can redistribute it and/or modify it under the terms of ** the GNU Affero General Public License as published by the Free Software Foundation, version 3. ** ** This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; ** without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. ** See the GNU Affero General Public License for more details. ** ** You should have received a copy of the GNU Affero General Public License along with this program. ** If not, see <https://www.gnu.org/licenses/>. **/ #include "zbxmutexs.h" #ifdef _WINDOWS # include "zbxsysinfo.h" # include "zbxlog.h" #else #ifdef HAVE_PTHREAD_PROCESS_SHARED typedef struct { pthread_mutex_t mutexes[ZBX_MUTEX_COUNT]; pthread_rwlock_t rwlocks[ZBX_RWLOCK_COUNT]; } zbx_shared_lock_t; static zbx_shared_lock_t *shared_lock; static int shm_id, locks_disabled; #else # if !HAVE_SEMUN union semun { int val; /* value for SETVAL */ struct semid_ds *buf; /* buffer for IPC_STAT & IPC_SET */ unsigned short int *array; /* array for GETALL & SETALL */ struct seminfo *__buf; /* buffer for IPC_INFO */ }; # undef HAVE_SEMUN # define HAVE_SEMUN 1 # endif /* HAVE_SEMUN */ # include "zbxcfg.h" # include "zbxthreads.h" static int ZBX_SEM_LIST_ID = -1; static unsigned char mutexes; #endif /****************************************************************************** * * * Purpose: if pthread mutexes and read-write locks can be shared between * * processes then create them, otherwise fallback to System V * * semaphore operations * * * * Parameters: error - [OUT] dynamically allocated memory with error message. * * * * Return value: SUCCEED if mutexes successfully created, otherwise FAIL * * * ******************************************************************************/ int zbx_locks_create(char **error) { #ifdef HAVE_PTHREAD_PROCESS_SHARED int i; pthread_mutexattr_t mta; pthread_rwlockattr_t rwa; if (-1 == (shm_id = shmget(IPC_PRIVATE, ZBX_SIZE_T_ALIGN8(sizeof(zbx_shared_lock_t)), IPC_CREAT | IPC_EXCL | 0600))) { *error = zbx_dsprintf(*error, "cannot allocate shared memory for locks"); return FAIL; } if ((void *)(-1) == (shared_lock = (zbx_shared_lock_t *)shmat(shm_id, NULL, 0))) { *error = zbx_dsprintf(*error, "cannot attach shared memory for locks: %s", zbx_strerror(errno)); return FAIL; } memset(shared_lock, 0, sizeof(zbx_shared_lock_t)); /* immediately mark the new shared memory for destruction after attaching to it */ if (-1 == shmctl(shm_id, IPC_RMID, 0)) { *error = zbx_dsprintf(*error, "cannot mark the new shared memory for destruction: %s", zbx_strerror(errno)); return FAIL; } if (0 != pthread_mutexattr_init(&mta)) { *error = zbx_dsprintf(*error, "cannot initialize mutex attribute: %s", zbx_strerror(errno)); return FAIL; } if (0 != pthread_mutexattr_setpshared(&mta, PTHREAD_PROCESS_SHARED)) { *error = zbx_dsprintf(*error, "cannot set shared mutex attribute: %s", zbx_strerror(errno)); return FAIL; } for (i = 0; i < ZBX_MUTEX_COUNT; i++) { if (0 != pthread_mutex_init(&shared_lock->mutexes[i], &mta)) { *error = zbx_dsprintf(*error, "cannot create mutex: %s", zbx_strerror(errno)); return FAIL; } } if (0 != pthread_rwlockattr_init(&rwa)) { *error = zbx_dsprintf(*error, "cannot initialize read write lock attribute: %s", zbx_strerror(errno)); return FAIL; } if (0 != pthread_rwlockattr_setpshared(&rwa, PTHREAD_PROCESS_SHARED)) { *error = zbx_dsprintf(*error, "cannot set shared read write lock attribute: %s", zbx_strerror(errno)); return FAIL; } for (i = 0; i < ZBX_RWLOCK_COUNT; i++) { if (0 != pthread_rwlock_init(&shared_lock->rwlocks[i], &rwa)) { *error = zbx_dsprintf(*error, "cannot create rwlock: %s", zbx_strerror(errno)); return FAIL; } } #else union semun semopts; int i; if (-1 == (ZBX_SEM_LIST_ID = semget(IPC_PRIVATE, ZBX_MUTEX_COUNT + ZBX_RWLOCK_COUNT, 0600))) { *error = zbx_dsprintf(*error, "cannot create semaphore set: %s", zbx_strerror(errno)); return FAIL; } /* set default semaphore value */ semopts.val = 1; for (i = 0; ZBX_MUTEX_COUNT + ZBX_RWLOCK_COUNT > i; i++) { if (-1 != semctl(ZBX_SEM_LIST_ID, i, SETVAL, semopts)) continue; *error = zbx_dsprintf(*error, "cannot initialize semaphore: %s", zbx_strerror(errno)); if (-1 == semctl(ZBX_SEM_LIST_ID, 0, IPC_RMID, 0)) zbx_error("cannot remove semaphore set %d: %s", ZBX_SEM_LIST_ID, zbx_strerror(errno)); ZBX_SEM_LIST_ID = -1; return FAIL; } #endif return SUCCEED; } /****************************************************************************** * * * Purpose: destroys process-shared locks * * * ******************************************************************************/ void zbx_locks_destroy(void) { #ifdef HAVE_PTHREAD_PROCESS_SHARED int i; if (NULL == shared_lock) return; for (i = 0; i < ZBX_MUTEX_COUNT; i++) (void)pthread_mutex_destroy(&shared_lock->mutexes[i]); for (i = 0; i < ZBX_RWLOCK_COUNT; i++) (void)pthread_rwlock_destroy(&shared_lock->rwlocks[i]); if (-1 == shmdt(shared_lock)) zabbix_log(LOG_LEVEL_TRACE, "cannot detach shared lock memory: %s", zbx_strerror(errno)); shared_lock = NULL; shm_id = 0; #else if (-1 == semctl(ZBX_SEM_LIST_ID, 0, IPC_RMID, 0)) zabbix_log(LOG_LEVEL_TRACE, "cannot remove semaphore set %d: %s", ZBX_SEM_LIST_ID, zbx_strerror(errno)); #endif } /****************************************************************************** * * * Purpose: acquire address of the mutex * * * * Parameters: mutex_name - [IN] name of the mutex to return address for * * * * Return value: address of the mutex * * * ******************************************************************************/ zbx_mutex_t zbx_mutex_addr_get(zbx_mutex_name_t mutex_name) { #ifdef HAVE_PTHREAD_PROCESS_SHARED return &shared_lock->mutexes[mutex_name]; #else return mutex_name; #endif } /****************************************************************************** * * * Purpose: acquire address of the rwlock * * * * Parameters: rwlock_name - [IN] name of the rwlock to return address for * * * * Return value: address of the rwlock * * * ******************************************************************************/ zbx_rwlock_t zbx_rwlock_addr_get(zbx_rwlock_name_t rwlock_name) { #ifdef HAVE_PTHREAD_PROCESS_SHARED return &shared_lock->rwlocks[rwlock_name]; #else return rwlock_name + ZBX_MUTEX_COUNT; #endif } /****************************************************************************** * * * Purpose: read-write locks are created using zbx_locks_create() function * * this is only to obtain handle, if read write locks are not * * supported, then outputs numeric handle of mutex that can be used * * with mutex handling functions * * * * Parameters: rwlock - [IN/OUT] read-write lock handle if supported, * * otherwise mutex * * name - [IN] name of read-write lock (index for nix system) * * error - [IN/OUT] unused * * * * Return value: SUCCEED if mutexes successfully created, otherwise FAIL * * * ******************************************************************************/ int zbx_rwlock_create(zbx_rwlock_t *rwlock, zbx_rwlock_name_t name, char **error) { ZBX_UNUSED(error); #ifdef HAVE_PTHREAD_PROCESS_SHARED *rwlock = &shared_lock->rwlocks[name]; #else *rwlock = name + ZBX_MUTEX_COUNT; mutexes++; #endif return SUCCEED; } #ifdef HAVE_PTHREAD_PROCESS_SHARED /****************************************************************************** * * * Purpose: acquire write lock for read-write lock (exclusive access) * * * * Parameters: filename - [IN] source filename (for tracking) * * line - [IN] source filename line number (for tracking) * * rwlock - [IN] handle of read-write lock * * * ******************************************************************************/ void __zbx_rwlock_wrlock(const char *filename, int line, zbx_rwlock_t rwlock) { if (ZBX_RWLOCK_NULL == rwlock) return; if (0 != locks_disabled) return; if (0 != pthread_rwlock_wrlock(rwlock)) { zbx_error("[file:'%s',line:%d] write lock failed: %s", filename, line, zbx_strerror(errno)); exit(EXIT_FAILURE); } } /****************************************************************************** * * * Purpose: acquire read lock for read-write lock (there can be many readers) * * * * Parameters: filename - [IN] source filename (for tracking) * * line - [IN] source filename line number (for tracking) * * rwlock - [IN] handle of read-write lock * * * ******************************************************************************/ void __zbx_rwlock_rdlock(const char *filename, int line, zbx_rwlock_t rwlock) { if (ZBX_RWLOCK_NULL == rwlock) return; if (0 != locks_disabled) return; if (0 != pthread_rwlock_rdlock(rwlock)) { zbx_error("[file:'%s',line:%d] read lock failed: %s", filename, line, zbx_strerror(errno)); exit(EXIT_FAILURE); } } /****************************************************************************** * * * Purpose: unlock read-write lock * * * * Parameters: filename - [IN] source filename (for tracking) * * line - [IN] source filename line number (for tracking) * * rwlock - [IN] handle of read-write lock * * * ******************************************************************************/ void __zbx_rwlock_unlock(const char *filename, int line, zbx_rwlock_t rwlock) { if (ZBX_RWLOCK_NULL == rwlock) return; if (0 != locks_disabled) return; if (0 != pthread_rwlock_unlock(rwlock)) { zbx_error("[file:'%s',line:%d] read-write lock unlock failed: %s", filename, line, zbx_strerror(errno)); exit(EXIT_FAILURE); } } /****************************************************************************** * * * Purpose: Destroy read-write lock * * * * Parameters: rwlock - [IN] handle of read-write lock * * * ******************************************************************************/ void zbx_rwlock_destroy(zbx_rwlock_t *rwlock) { if (ZBX_RWLOCK_NULL == *rwlock) return; *rwlock = ZBX_RWLOCK_NULL; } /****************************************************************************** * * * Purpose: disable locks * * * ******************************************************************************/ void zbx_locks_disable(void) { /* attempting to destroy a locked pthread mutex results in undefined behavior */ locks_disabled = 1; } /****************************************************************************** * * * Purpose: enable locks * * * ******************************************************************************/ void zbx_locks_enable(void) { /* attempting to destroy a locked pthread mutex results in undefined behavior */ locks_disabled = 0; } #endif #endif /* _WINDOWS */ /****************************************************************************** * * * Purpose: Create the mutex * * * * Parameters: mutex - [IN/OUT] handle of mutex * * name - [IN] name of mutex (index for nix system) * * error - [OUT] the error message * * * * Return value: If the function succeeds, then return SUCCEED, * * FAIL on an error * * * ******************************************************************************/ int zbx_mutex_create(zbx_mutex_t *mutex, zbx_mutex_name_t name, char **error) { #ifdef _WINDOWS if (NULL == (*mutex = CreateMutex(NULL, FALSE, name))) { *error = zbx_dsprintf(*error, "error on mutex creating: %s", zbx_strerror_from_system(GetLastError())); return FAIL; } #else ZBX_UNUSED(error); #ifdef HAVE_PTHREAD_PROCESS_SHARED *mutex = &shared_lock->mutexes[name]; #else mutexes++; *mutex = name; #endif #endif return SUCCEED; } /****************************************************************************** * * * Purpose: Waits until the mutex is in the signalled state * * * * Parameters: filename - [IN] source filename (for tracking) * * line - [IN] source filename line number (for tracking) * * mutex - [IN] handle of read-write lock * * * ******************************************************************************/ void __zbx_mutex_lock(const char *filename, int line, zbx_mutex_t mutex) { #ifndef _WINDOWS #ifndef HAVE_PTHREAD_PROCESS_SHARED struct sembuf sem_lock; #endif #else DWORD dwWaitResult; #endif if (ZBX_MUTEX_NULL == mutex) return; #ifdef _WINDOWS #ifdef ZABBIX_AGENT if (0 != (ZBX_MUTEX_THREAD_DENIED & zbx_get_thread_global_mutex_flag())) { zbx_error("[file:'%s',line:%d] lock failed: ZBX_MUTEX_THREAD_DENIED is set for thread with id = %d", filename, line, zbx_get_thread_id()); exit(EXIT_FAILURE); } #endif dwWaitResult = WaitForSingleObject(mutex, INFINITE); switch (dwWaitResult) { case WAIT_OBJECT_0: break; case WAIT_ABANDONED: THIS_SHOULD_NEVER_HAPPEN; exit(EXIT_FAILURE); default: zbx_error("[file:'%s',line:%d] lock failed: %s", filename, line, zbx_strerror_from_system(GetLastError())); exit(EXIT_FAILURE); } #else #ifdef HAVE_PTHREAD_PROCESS_SHARED if (0 != locks_disabled) return; if (0 != pthread_mutex_lock(mutex)) { zbx_error("[file:'%s',line:%d] lock failed: %s", filename, line, zbx_strerror(errno)); exit(EXIT_FAILURE); } #else sem_lock.sem_num = mutex; sem_lock.sem_op = -1; sem_lock.sem_flg = SEM_UNDO; while (-1 == semop(ZBX_SEM_LIST_ID, &sem_lock, 1)) { if (EINTR != errno) { zbx_error("[file:'%s',line:%d] lock failed: %s", filename, line, zbx_strerror(errno)); exit(EXIT_FAILURE); } } #endif #endif } /****************************************************************************** * * * Purpose: Unlock the mutex * * * * Parameters: filename - [IN] source filename (for tracking) * * line - [IN] source filename line number (for tracking) * * mutex - [IN] handle of read-write lock * * * ******************************************************************************/ void __zbx_mutex_unlock(const char *filename, int line, zbx_mutex_t mutex) { #ifndef _WINDOWS #ifndef HAVE_PTHREAD_PROCESS_SHARED struct sembuf sem_unlock; #endif #endif if (ZBX_MUTEX_NULL == mutex) return; #ifdef _WINDOWS if (0 == ReleaseMutex(mutex)) { zbx_error("[file:'%s',line:%d] unlock failed: %s", filename, line, zbx_strerror_from_system(GetLastError())); exit(EXIT_FAILURE); } #else #ifdef HAVE_PTHREAD_PROCESS_SHARED if (0 != locks_disabled) return; if (0 != pthread_mutex_unlock(mutex)) { zbx_error("[file:'%s',line:%d] unlock failed: %s", filename, line, zbx_strerror(errno)); exit(EXIT_FAILURE); } #else sem_unlock.sem_num = mutex; sem_unlock.sem_op = 1; sem_unlock.sem_flg = SEM_UNDO; while (-1 == semop(ZBX_SEM_LIST_ID, &sem_unlock, 1)) { if (EINTR != errno) { zbx_error("[file:'%s',line:%d] unlock failed: %s", filename, line, zbx_strerror(errno)); exit(EXIT_FAILURE); } } #endif #endif } /****************************************************************************** * * * Purpose: Destroy the mutex * * * * Parameters: mutex - [IN/OUT] handle of mutex * * * ******************************************************************************/ void zbx_mutex_destroy(zbx_mutex_t *mutex) { #ifdef _WINDOWS if (ZBX_MUTEX_NULL == *mutex) return; if (0 == CloseHandle(*mutex)) zbx_error("error on mutex destroying: %s", zbx_strerror_from_system(GetLastError())); #endif *mutex = ZBX_MUTEX_NULL; } #ifdef _WINDOWS /****************************************************************************** * * * Purpose: Appends PID to the prefix of the mutex * * * * Parameters: prefix - [IN] mutex type * * * * Return value: Dynamically allocated, NUL terminated name of the mutex * * * * Comments: The mutex name must be shorter than MAX_PATH characters, * * otherwise the function calls exit() * * * ******************************************************************************/ zbx_mutex_name_t zbx_mutex_create_per_process_name(const zbx_mutex_name_t prefix) { zbx_mutex_name_t name = ZBX_MUTEX_NULL; int size; wchar_t *format = L"%s_PID_%lx"; DWORD pid = GetCurrentProcessId(); /* exit if the mutex name length exceed the maximum allowed */ size = _scwprintf(format, prefix, pid); if (MAX_PATH < size) { THIS_SHOULD_NEVER_HAPPEN; exit(EXIT_FAILURE); } size = size + 1; /* for terminating '\0' */ name = zbx_malloc(NULL, sizeof(wchar_t) * size); (void)_snwprintf_s(name, size, size - 1, format, prefix, pid); name[size - 1] = L'\0'; return name; } #endif