/* * Copyright (c) 2015, Xilinx Inc. and Contributors. All rights reserved. * * SPDX-License-Identifier: BSD-3-Clause */ /* * @file utils.c * @brief Linux libmetal utility functions. */ #include #include /** * @brief Open (or create) a file. * * This function opens or creates a file with read/write permissions and the * O_CLOEXEC flag set. * * @param[in] path File path to open. * @param[in] shm Open shared memory (via shm_open) if non-zero. * @return File descriptor. */ int metal_open(const char *path, int shm) { const int flags = O_RDWR | O_CREAT | O_CLOEXEC; const int mode = S_IRUSR | S_IWUSR; int fd; if (!path || !strlen(path)) return -EINVAL; fd = shm ? shm_open(path, flags, mode) : open(path, flags, mode); return fd < 0 ? -errno : fd; } /** * @brief Open (or create) and unlink a file. * * This function opens or creates a file with read/write permissions and the * O_CLOEXEC flag set. This file is then unlinked to ensure that it is removed * when the last reference to the file is dropped. This ensures that libmetal * shared maps are released appropriately once all users of the shared data * exit. * * @param[in] path File path to open. * @param[in] shm Open shared memory (via shm_open) if non-zero. * @return File descriptor. */ int metal_open_unlinked(const char *path, int shm) { int fd, error; fd = metal_open(path, shm); if (fd < 0) return fd; error = shm ? shm_unlink(path) : unlink(path); error = error < 0 ? -errno : 0; if (error) close(fd); return error ? error : fd; } /** * @brief Randomize a string. * * This function randomizes the contents of a string. * * @param[in] template String to be randomized. */ void metal_randomize_string(char *template) { const char *chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz" "0123456789-="; while (*template) { *template++ = chars[rand() % 64]; } } /** * @brief Create a file name template suitable for use with metal_mktemp. * * @param[out] template Template string to be filled in. * @param[in] name Module/user identifier used as part of the * generated template. */ void metal_mktemp_template(char template[PATH_MAX], const char *name) { snprintf(template, PATH_MAX, "%s/metal-%s-XXXXXX", _metal.tmp_path, name); } /** * @brief Create a temporary file or fifo. * * This function creates a temporary file or fifo, and sets the O_CLOEXEC flag * on it. * * @param[in] template File name template (the last 6 characters must * be XXXXXX per mkstemp requirements). * @param[in] fifo Non-zero to create a FIFO instead of a regular * file. * @return File descriptor. */ int metal_mktemp(char *template, int fifo) { const int mode = S_IRUSR | S_IWUSR; int result, len, flags; char *suffix; if (!template) return -EINVAL; len = strlen(template); suffix = &template[len - 6]; if (len < 6 || strcmp(suffix, "XXXXXX")) { metal_log(METAL_LOG_ERROR, "template %s has no trailing pattern\n", template); return -EINVAL; } flags = (fifo ? O_RDONLY | O_NONBLOCK | O_CLOEXEC : O_CREAT | O_EXCL | O_RDWR | O_CLOEXEC); for (;;) { metal_randomize_string(suffix); if (fifo) { result = mkfifo(template, mode); if (result < 0) { if (errno == EEXIST) continue; metal_log(METAL_LOG_ERROR, "mkfifo(%s) failed (%s)\n", template, strerror(errno)); return -errno; } } result = open(template, flags, mode); if (result < 0) { if (fifo) unlink(template); if (errno == EEXIST) continue; metal_log(METAL_LOG_ERROR, "open() failed (%s)\n", strerror(errno)); return -errno; } return result; } } /** * @brief Open (or create) and unlink a temporary file. * * This function creates a temporary file, and sets the O_CLOEXEC flag on it. * O_CLOEXEC flag set. This file is then unlinked to ensure that it is removed * when the last reference to the file is dropped. This ensures that libmetal * shared maps are released appropriately once all users of the shared data * exit. * * @param[in] template File name template (the last 6 characters must * be XXXXXX per mkstemp requirements). * @return File descriptor. */ int metal_mktemp_unlinked(char *template) { int fd, error; fd = metal_mktemp(template, 0); if (fd < 0) return fd; error = unlink(template) < 0 ? -errno : 0; if (error) close(fd); return error ? error : fd; } /** * @brief Map a segment of a file/device. * * This function maps a segment of a file or device into the process address * space, after optionally expanding the file if necessary. If required, the * file is expanded to hold the requested map area. This is done under and * advisory lock, and therefore the called must not have an advisory lock on * the file being mmapped. * * @param[in] fd File descriptor to map. * @param[in] offset Offset in file to map. * @param[in] size Size of region to map. * @param[in] expand Allow file expansion via ftruncate if non-zero. * @param[in] flags Flags for mmap(), MAP_SHARED included implicitly. * @param[out] result Returned pointer to new memory map. * @return 0 on success, or -errno on error. */ int metal_map(int fd, off_t offset, size_t size, int expand, int flags, void **result) { int prot = PROT_READ | PROT_WRITE, error; void *mem; flags |= MAP_SHARED; if (fd < 0) { fd = -1; flags = MAP_PRIVATE | MAP_ANONYMOUS; } else if (expand) { off_t reqsize = offset + size; struct stat stat; error = flock(fd, LOCK_EX) < 0 ? -errno : 0; if (!error) error = fstat(fd, &stat); if (!error && stat.st_size < reqsize) error = ftruncate(fd, reqsize); if (!error) flock(fd, LOCK_UN); if (error) return -errno; } mem = mmap(NULL, size, prot, flags, fd, offset); if (mem == MAP_FAILED) return -errno; *result = mem; return 0; } /** * @brief Unmap a segment of the process address space. * * This function unmaps a segment of the process address space. * * @param[in] mem Segment to unmap. * @param[in] size Size of region to unmap. * @return 0 on success, or -errno on error. */ int metal_unmap(void *mem, size_t size) { return munmap(mem, size) < 0 ? -errno : 0; } /** * @brief Lock in a region of the process address space. * * @param[in] mem Pointer to start of region. * @param[in] size Size of region. * @return 0 on success, or -errno on error. */ int metal_mlock(void *mem, size_t size) { return mlock(mem, size) ? -errno : 0; } int metal_virt2phys(void *addr, unsigned long *phys) { off_t offset; uint64_t entry; int error; if (_metal.pagemap_fd < 0) return -ENOSYS; offset = ((uintptr_t)addr >> _metal.page_shift) * sizeof(entry); error = pread(_metal.pagemap_fd, &entry, sizeof(entry), offset); if (error < 0) { metal_log(METAL_LOG_ERROR, "failed pagemap pread (offset %llx) - %s\n", (unsigned long long)offset, strerror(errno)); return -errno; } /* Check page present and not swapped. */ if ((entry >> 62) != 2) { metal_log(METAL_LOG_ERROR, "pagemap page not present, %llx -> %llx\n", (unsigned long long)offset, (unsigned long long)entry); return -ENOENT; } *phys = (entry & ((1ULL << 54) - 1)) << _metal.page_shift; return 0; }