/*
** 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 "zbxnix.h"

#include "zbxcommon.h"

/******************************************************************************
 *                                                                            *
 * Purpose: creates dynamic shared memory segment                             *
 *                                                                            *
 * Parameters: shm       - [OUT] the dynamic shared memory data               *
 *             shm_size  - [IN] the initial size (can be 0)                   *
 *             mutex     - [IN] the name of mutex used to synchronize memory  *
 *                              access                                        *
 *             copy_func - [IN] the function used to copy shared memory       *
 *                              contents during reallocation                  *
 *             errmsg    - [OUT] the error message                            *
 *                                                                            *
 * Return value: SUCCEED - the dynamic shared memory segment was created      *
 *                         successfully.                                      *
 *               FAIL    - otherwise. The errmsg contains error message and   *
 *                         must be freed by the caller.                       *
 *                                                                            *
 ******************************************************************************/
int	zbx_dshm_create(zbx_dshm_t *shm, size_t shm_size, zbx_mutex_name_t mutex,
		zbx_shm_copy_func_t copy_func, char **errmsg)
{
	int	ret = FAIL;

	zabbix_log(LOG_LEVEL_DEBUG, "In %s() size:" ZBX_FS_SIZE_T, __func__, (zbx_fs_size_t)shm_size);

	if (SUCCEED != zbx_mutex_create(&shm->lock, mutex, errmsg))
		goto out;

	if (0 < shm_size)
	{
		if (-1 == (shm->shmid = zbx_shm_create(shm_size)))
		{
			*errmsg = zbx_strdup(*errmsg, "cannot allocate shared memory");
			goto out;
		}
	}
	else
		shm->shmid = ZBX_NONEXISTENT_SHMID;

	shm->size = shm_size;
	shm->copy_func = copy_func;

	ret = SUCCEED;
out:
	zabbix_log(LOG_LEVEL_DEBUG, "End of %s():%s shmid:%d", __func__, zbx_result_string(ret), shm->shmid);

	return ret;
}

/******************************************************************************
 *                                                                            *
 * Purpose: destroys dynamic shared memory segment                            *
 *                                                                            *
 * Parameters: shm    - [IN] the dynamic shared memory data                   *
 *             errmsg - [OUT] the error message                               *
 *                                                                            *
 * Return value: SUCCEED - the dynamic shared memory segment was destroyed    *
 *                         successfully.                                      *
 *               FAIL    - otherwise. The errmsg contains error message and   *
 *                         must be freed by the caller.                       *
 *                                                                            *
 ******************************************************************************/
int	zbx_dshm_destroy(zbx_dshm_t *shm, char **errmsg)
{
	int	ret = FAIL;

	zabbix_log(LOG_LEVEL_DEBUG, "In %s() shmid:%d", __func__, shm->shmid);

	zbx_mutex_destroy(&shm->lock);

	if (ZBX_NONEXISTENT_SHMID != shm->shmid)
	{
		if (-1 == shmctl(shm->shmid, IPC_RMID, NULL))
		{
			*errmsg = zbx_dsprintf(*errmsg, "cannot remove shared memory: %s", zbx_strerror(errno));
			goto out;
		}
		shm->shmid = ZBX_NONEXISTENT_SHMID;
	}

	ret = SUCCEED;
out:
	zabbix_log(LOG_LEVEL_DEBUG, "End of %s():%s", __func__, zbx_result_string(ret));

	return ret;
}

void	zbx_dshm_lock(zbx_dshm_t *shm)
{
	zbx_mutex_lock(shm->lock);
}

void	zbx_dshm_unlock(zbx_dshm_t *shm)
{
	zbx_mutex_unlock(shm->lock);
}

/******************************************************************************
 *                                                                            *
 * Purpose: validates local reference to dynamic shared memory segment        *
 *                                                                            *
 * Parameters: shm     - [IN] the dynamic shared memory data                  *
 *             shm_ref - [IN/OUT] a local reference to dynamic shared memory  *
 *                                segment                                     *
 *             errmsg  - [OUT] the error message                              *
 *                                                                            *
 * Return value: SUCCEED - the local reference to dynamic shared memory       *
 *                         segment was validated successfully and contains    *
 *                         correct dynamic shared memory segment address      *
 *               FAIL    - otherwise. The errmsg contains error message and   *
 *                         must be freed by the caller.                       *
 *                                                                            *
 * Comments: This function should be called before accessing the dynamic      *
 *           shared memory to ensure that the local reference has correct     *
 *           address after shared memory allocation/reallocation.             *
 *                                                                            *
 ******************************************************************************/
int	zbx_dshm_validate_ref(const zbx_dshm_t *shm, zbx_dshm_ref_t *shm_ref, char **errmsg)
{
	int	ret = FAIL;

	zabbix_log(LOG_LEVEL_TRACE, "In %s() shmid:%d refid:%d", __func__, shm->shmid, shm_ref->shmid);

	if (shm->shmid != shm_ref->shmid)
	{
		if (ZBX_NONEXISTENT_SHMID != shm_ref->shmid)
		{
			if (-1 == shmdt((void *)shm_ref->addr))
			{
				*errmsg = zbx_dsprintf(*errmsg, "cannot detach shared memory: %s", zbx_strerror(errno));
				goto out;
			}
			shm_ref->addr = NULL;
			shm_ref->shmid = ZBX_NONEXISTENT_SHMID;
		}

		if ((void *)(-1) == (shm_ref->addr = shmat(shm->shmid, NULL, 0)))
		{
			*errmsg = zbx_dsprintf(*errmsg, "cannot attach shared memory: %s", zbx_strerror(errno));
			shm_ref->addr = NULL;
			goto out;
		}

		shm_ref->shmid = shm->shmid;
	}

	ret = SUCCEED;
out:
	zabbix_log(LOG_LEVEL_TRACE, "End of %s():%s", __func__, zbx_result_string(ret));

	return ret;
}

/******************************************************************************
 *                                                                            *
 * Purpose: reallocates dynamic shared memory segment                         *
 *                                                                            *
 * Parameters: shm      - [IN/OUT] the dynamic shared memory data             *
 *             size     - [IN] the new segment size                           *
 *             errmsg   - [OUT] the error message                             *
 *                                                                            *
 * Return value:                                                              *
 *    SUCCEED - the shared memory segment was successfully reallocated.       *
 *    FAIL    - otherwise. The errmsg contains error message and must be      *
 *              freed by the caller.                                          *
 *                                                                            *
 * Comments: The shared memory segment is reallocated by simply creating      *
 *           a new segment and copying the data from old segment by calling   *
 *           the copy_data callback function.                                 *
 *                                                                            *
 ******************************************************************************/
int	zbx_dshm_realloc(zbx_dshm_t *shm, size_t size, char **errmsg)
{
	int	shmid, ret = FAIL;
	void	*addr, *addr_old = NULL;
	size_t	shm_size;

	zabbix_log(LOG_LEVEL_DEBUG, "In %s() shmid:%d size:" ZBX_FS_SIZE_T, __func__, shm->shmid, (zbx_fs_size_t)size);

	shm_size = ZBX_SIZE_T_ALIGN8(size);

	/* attach to the old segment if possible */
	if (ZBX_NONEXISTENT_SHMID != shm->shmid && (void *)(-1) == (addr_old = shmat(shm->shmid, NULL, 0)))
	{
		*errmsg = zbx_dsprintf(*errmsg, "cannot attach current shared memory: %s", zbx_strerror(errno));
		goto out;
	}

	if (-1 == (shmid = zbx_shm_create(shm_size)))
	{
		*errmsg = zbx_strdup(NULL, "cannot allocate shared memory");
		goto out;
	}

	if ((void *)(-1) == (addr = shmat(shmid, NULL, 0)))
	{
		if (NULL != addr_old)
			(void)shmdt(addr_old);

		*errmsg = zbx_dsprintf(*errmsg, "cannot attach new shared memory: %s", zbx_strerror(errno));
		goto out;
	}

	/* copy data from the old segment */
	shm->copy_func(addr, shm_size, addr_old);

	if (-1 == shmdt((void *)addr))
	{
		*errmsg = zbx_strdup(*errmsg, "cannot detach from new shared memory");
		goto out;
	}

	/* delete the old segment */
	if (NULL != addr_old && -1 == zbx_shm_destroy(shm->shmid))
	{
		*errmsg = zbx_strdup(*errmsg, "cannot detach from old shared memory");
		goto out;
	}

	shm->size = shm_size;
	shm->shmid = shmid;

	ret = SUCCEED;
out:
	zabbix_log(LOG_LEVEL_DEBUG, "End of %s():%s shmid:%d", __func__, zbx_result_string(ret), shm->shmid);

	return ret;
}