// Copyright 2019 The SwiftShader Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "System/Debug.hpp"

#include <errno.h>
#include <fcntl.h>
#include <string.h>
#include <sys/mman.h>
#include <unistd.h>

#ifndef __APPLE__
#	error "This file is for macOS only!"
#endif  // __APPLE__

#if __MAC_OS_X_VERSION_MIN_REQUIRED < __MAC_10_12
#	include <mach/mach_time.h>
#endif  // __MAC_OS_X_VERSION_MIN_REQUIRED < __MAC_10_12

namespace {

struct timespec GetTime()
{
	struct timespec tv;

#if __MAC_OS_X_VERSION_MIN_REQUIRED >= __MAC_10_12
	clock_gettime(CLOCK_REALTIME, &tv);
#else
	mach_timebase_info_data_t timebase;
	mach_timebase_info(&timebase);
	uint64_t time;
	time = mach_absolute_time();

	double convert_ratio = (double)timebase.numer / (double)timebase.denom;
	uint64_t secs = (uint64_t)((double)time * convert_ratio / 1e-9);
	uint64_t usecs = (uint64_t)((double)time * convert_ratio - secs * 1e9);
	tv.tv_sec = secs;
	tv.tv_nsec = usecs;
#endif
	return tv;
}

}  // namespace

// An implementation of OpaqueFdExternalMemory that relies on shm_open().
// Useful on OS X which do not have Linux memfd regions.
class OpaqueFdExternalMemory : public vk::DeviceMemory::ExternalBase
{
public:
	static const VkExternalMemoryHandleTypeFlagBits typeFlagBit = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT;

	static bool SupportsAllocateInfo(const VkMemoryAllocateInfo *pAllocateInfo)
	{
		OpaqueFdAllocateInfo info(pAllocateInfo);
		return info.importFd || info.exportFd;
	}

	explicit OpaqueFdExternalMemory(const VkMemoryAllocateInfo *pAllocateInfo)
	    : allocateInfo(pAllocateInfo)
	{
	}

	~OpaqueFdExternalMemory()
	{
		if(shm_fd_ >= 0)
		{
			::close(shm_fd_);
			shm_fd_ = -1;
		}
	}

	VkResult allocate(size_t size, void **pBuffer) override
	{
		if(allocateInfo.importFd)
		{
			shm_fd_ = allocateInfo.fd;
			if(shm_fd_ < 0)
			{
				return VK_ERROR_INVALID_EXTERNAL_HANDLE;
			}
		}
		else
		{
			ASSERT(allocateInfo.exportFd);
			// Create shared memory region with shm_open() and a randomly-generated region name.
			static const char kPrefix[] = "/SwiftShader-";
			const size_t kPrefixSize = sizeof(kPrefix) - 1;
			const size_t kRandomSize = 8;

			char name[kPrefixSize + kRandomSize + 1u];
			memcpy(name, kPrefix, kPrefixSize);

			int fd = -1;
			for(int tries = 0; tries < 6; ++tries)
			{
				struct timespec tv = GetTime();
				uint64_t r = (uint64_t)tv.tv_sec + (uint64_t)tv.tv_nsec;
				for(size_t pos = 0; pos < kRandomSize; ++pos, r /= 8)
				{
					name[kPrefixSize + pos] = '0' + (r % 8);
				}
				name[kPrefixSize + kRandomSize] = '\0';

				fd = shm_open(name, O_RDWR | O_CREAT | O_EXCL | O_NOFOLLOW, 0600);
				if(fd >= 0)
					break;

				if(errno != EEXIST)
				{
					TRACE("shm_open() failed with: %s", strerror(errno));
					break;
				}
			}

			// Unlink the name since it's not needed anymore.
			if(fd >= 0)
			{
				if(shm_unlink(name) == -1)
				{
					TRACE("shm_unlink() failed with: %s", strerror(errno));
					close(fd);
					fd = -1;
				}
			}

			// Ensure there is enough space.
			if(fd >= 0 && size > 0)
			{
				if(::ftruncate(fd, size) < 0)
				{
					TRACE("ftruncate() failed with: %s", strerror(errno));
					close(fd);
					fd = -1;
				}
			}

			if(fd < 0)
			{
				TRACE("Could not allocate shared memory region");
				return VK_ERROR_OUT_OF_DEVICE_MEMORY;
			}

			shm_fd_ = fd;
		}

		void *addr = ::mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED,
		                    shm_fd_, 0);

		if(addr == MAP_FAILED)
		{
			return VK_ERROR_MEMORY_MAP_FAILED;
		}
		*pBuffer = addr;
		return VK_SUCCESS;
	}

	void deallocate(void *buffer, size_t size) override
	{
		::munmap(buffer, size);
	}

	VkExternalMemoryHandleTypeFlagBits getFlagBit() const override
	{
		return typeFlagBit;
	}

	VkResult exportFd(int *pFd) const override
	{
		int fd = dup(shm_fd_);
		if(fd < 0)
		{
			return VK_ERROR_INVALID_EXTERNAL_HANDLE;
		}

		// Set the clo-on-exec flag.
		int flags = ::fcntl(fd, F_GETFD);
		::fcntl(fd, F_SETFL, flags | FD_CLOEXEC);

		*pFd = fd;
		return VK_SUCCESS;
	}

private:
	int shm_fd_ = -1;
	OpaqueFdAllocateInfo allocateInfo;
};