root/arch/um/os-Linux/file.c
// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (C) 2002 - 2007 Jeff Dike (jdike@{addtoit,linux.intel}.com)
 */

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <linux/falloc.h>
#include <sys/ioctl.h>
#include <sys/mount.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/sysmacros.h>
#include <sys/un.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/eventfd.h>
#include <poll.h>
#include <os.h>

static void copy_stat(struct uml_stat *dst, const struct stat64 *src)
{
        *dst = ((struct uml_stat) {
                .ust_dev     = src->st_dev,     /* device */
                .ust_ino     = src->st_ino,     /* inode */
                .ust_mode    = src->st_mode,    /* protection */
                .ust_nlink   = src->st_nlink,   /* number of hard links */
                .ust_uid     = src->st_uid,     /* user ID of owner */
                .ust_gid     = src->st_gid,     /* group ID of owner */
                .ust_size    = src->st_size,    /* total size, in bytes */
                .ust_blksize = src->st_blksize, /* blocksize for filesys I/O */
                .ust_blocks  = src->st_blocks,  /* number of blocks allocated */
                .ust_atime   = src->st_atime,   /* time of last access */
                .ust_mtime   = src->st_mtime,   /* time of last modification */
                .ust_ctime   = src->st_ctime,   /* time of last change */
        });
}

int os_stat_fd(const int fd, struct uml_stat *ubuf)
{
        struct stat64 sbuf;
        int err;

        CATCH_EINTR(err = fstat64(fd, &sbuf));
        if (err < 0)
                return -errno;

        if (ubuf != NULL)
                copy_stat(ubuf, &sbuf);
        return err;
}

int os_stat_file(const char *file_name, struct uml_stat *ubuf)
{
        struct stat64 sbuf;
        int err;

        CATCH_EINTR(err = stat64(file_name, &sbuf));
        if (err < 0)
                return -errno;

        if (ubuf != NULL)
                copy_stat(ubuf, &sbuf);
        return err;
}

int os_access(const char *file, int mode)
{
        int amode, err;

        amode = (mode & OS_ACC_R_OK ? R_OK : 0) |
                (mode & OS_ACC_W_OK ? W_OK : 0) |
                (mode & OS_ACC_X_OK ? X_OK : 0) |
                (mode & OS_ACC_F_OK ? F_OK : 0);

        err = access(file, amode);
        if (err < 0)
                return -errno;

        return 0;
}

/* FIXME? required only by hostaudio (because it passes ioctls verbatim) */
int os_ioctl_generic(int fd, unsigned int cmd, unsigned long arg)
{
        int err;

        err = ioctl(fd, cmd, arg);
        if (err < 0)
                return -errno;

        return err;
}

/* FIXME: ensure namebuf in os_get_if_name is big enough */
int os_get_ifname(int fd, char* namebuf)
{
        if (ioctl(fd, SIOCGIFNAME, namebuf) < 0)
                return -errno;

        return 0;
}

int os_mode_fd(int fd, int mode)
{
        int err;

        CATCH_EINTR(err = fchmod(fd, mode));
        if (err < 0)
                return -errno;

        return 0;
}

int os_file_type(char *file)
{
        struct uml_stat buf;
        int err;

        err = os_stat_file(file, &buf);
        if (err < 0)
                return err;

        if (S_ISDIR(buf.ust_mode))
                return OS_TYPE_DIR;
        else if (S_ISLNK(buf.ust_mode))
                return OS_TYPE_SYMLINK;
        else if (S_ISCHR(buf.ust_mode))
                return OS_TYPE_CHARDEV;
        else if (S_ISBLK(buf.ust_mode))
                return OS_TYPE_BLOCKDEV;
        else if (S_ISFIFO(buf.ust_mode))
                return OS_TYPE_FIFO;
        else if (S_ISSOCK(buf.ust_mode))
                return OS_TYPE_SOCK;
        else return OS_TYPE_FILE;
}

int os_file_mode(const char *file, struct openflags *mode_out)
{
        int err;

        *mode_out = OPENFLAGS();

        err = access(file, W_OK);
        if (err && (errno != EACCES))
                return -errno;
        else if (!err)
                *mode_out = of_write(*mode_out);

        err = access(file, R_OK);
        if (err && (errno != EACCES))
                return -errno;
        else if (!err)
                *mode_out = of_read(*mode_out);

        return err;
}

int os_open_file(const char *file, struct openflags flags, int mode)
{
        int fd, err, f = 0;

        if (flags.r && flags.w)
                f = O_RDWR;
        else if (flags.r)
                f = O_RDONLY;
        else if (flags.w)
                f = O_WRONLY;
        else f = 0;

        if (flags.s)
                f |= O_SYNC;
        if (flags.c)
                f |= O_CREAT;
        if (flags.t)
                f |= O_TRUNC;
        if (flags.e)
                f |= O_EXCL;
        if (flags.a)
                f |= O_APPEND;

        fd = open64(file, f, mode);
        if (fd < 0)
                return -errno;

        if (flags.cl && fcntl(fd, F_SETFD, 1)) {
                err = -errno;
                close(fd);
                return err;
        }

        return fd;
}

int os_connect_socket(const char *name)
{
        struct sockaddr_un sock;
        int fd, err;

        sock.sun_family = AF_UNIX;
        snprintf(sock.sun_path, sizeof(sock.sun_path), "%s", name);

        fd = socket(AF_UNIX, SOCK_STREAM, 0);
        if (fd < 0) {
                err = -errno;
                goto out;
        }

        err = connect(fd, (struct sockaddr *) &sock, sizeof(sock));
        if (err) {
                err = -errno;
                goto out_close;
        }

        return fd;

out_close:
        close(fd);
out:
        return err;
}

int os_dup_file(int fd)
{
        int new_fd = dup(fd);

        if (new_fd < 0)
                return -errno;

        return new_fd;
}

void os_close_file(int fd)
{
        close(fd);
}

int os_seek_file(int fd, unsigned long long offset)
{
        unsigned long long actual;

        actual = lseek64(fd, offset, SEEK_SET);
        if (actual != offset)
                return -errno;
        return 0;
}

int os_read_file(int fd, void *buf, int len)
{
        int n = read(fd, buf, len);

        if (n < 0)
                return -errno;
        return n;
}

int os_pread_file(int fd, void *buf, int len, unsigned long long offset)
{
        int n = pread(fd, buf, len, offset);

        if (n < 0)
                return -errno;
        return n;
}

int os_write_file(int fd, const void *buf, int len)
{
        int n = write(fd, (void *) buf, len);

        if (n < 0)
                return -errno;
        return n;
}

int os_sync_file(int fd)
{
        int n = fdatasync(fd);

        if (n < 0)
                return -errno;
        return n;
}

int os_pwrite_file(int fd, const void *buf, int len, unsigned long long offset)
{
        int n = pwrite(fd, (void *) buf, len, offset);

        if (n < 0)
                return -errno;
        return n;
}


int os_file_size(const char *file, unsigned long long *size_out)
{
        struct uml_stat buf;
        int err;

        err = os_stat_file(file, &buf);
        if (err < 0) {
                printk(UM_KERN_ERR "Couldn't stat \"%s\" : err = %d\n", file,
                       -err);
                return err;
        }

        if (S_ISBLK(buf.ust_mode)) {
                int fd;
                long blocks;

                fd = open(file, O_RDONLY, 0);
                if (fd < 0) {
                        err = -errno;
                        printk(UM_KERN_ERR "Couldn't open \"%s\", "
                               "errno = %d\n", file, errno);
                        return err;
                }
                if (ioctl(fd, BLKGETSIZE, &blocks) < 0) {
                        err = -errno;
                        printk(UM_KERN_ERR "Couldn't get the block size of "
                               "\"%s\", errno = %d\n", file, errno);
                        close(fd);
                        return err;
                }
                *size_out = ((long long) blocks) * 512;
                close(fd);
        }
        else *size_out = buf.ust_size;

        return 0;
}

int os_file_modtime(const char *file, long long *modtime)
{
        struct uml_stat buf;
        int err;

        err = os_stat_file(file, &buf);
        if (err < 0) {
                printk(UM_KERN_ERR "Couldn't stat \"%s\" : err = %d\n", file,
                       -err);
                return err;
        }

        *modtime = buf.ust_mtime;
        return 0;
}

int os_set_exec_close(int fd)
{
        int err;

        CATCH_EINTR(err = fcntl(fd, F_SETFD, FD_CLOEXEC));

        if (err < 0)
                return -errno;
        return err;
}

int os_pipe(int *fds, int stream, int close_on_exec)
{
        int err, type = stream ? SOCK_STREAM : SOCK_DGRAM;

        err = socketpair(AF_UNIX, type, 0, fds);
        if (err < 0)
                return -errno;

        if (!close_on_exec)
                return 0;

        err = os_set_exec_close(fds[0]);
        if (err < 0)
                goto error;

        err = os_set_exec_close(fds[1]);
        if (err < 0)
                goto error;

        return 0;

 error:
        printk(UM_KERN_ERR "os_pipe : Setting FD_CLOEXEC failed, err = %d\n",
               -err);
        close(fds[1]);
        close(fds[0]);
        return err;
}

int os_set_fd_async(int fd)
{
        int err, flags;

        flags = fcntl(fd, F_GETFL);
        if (flags < 0)
                return -errno;

        flags |= O_ASYNC | O_NONBLOCK;
        if (fcntl(fd, F_SETFL, flags) < 0) {
                err = -errno;
                printk(UM_KERN_ERR "os_set_fd_async : failed to set O_ASYNC "
                       "and O_NONBLOCK on fd # %d, errno = %d\n", fd, errno);
                return err;
        }

        if ((fcntl(fd, F_SETSIG, SIGIO) < 0) ||
            (fcntl(fd, F_SETOWN, os_getpid()) < 0)) {
                err = -errno;
                printk(UM_KERN_ERR "os_set_fd_async : Failed to fcntl F_SETOWN "
                       "(or F_SETSIG) fd %d, errno = %d\n", fd, errno);
                return err;
        }

        return 0;
}

int os_clear_fd_async(int fd)
{
        int flags;

        flags = fcntl(fd, F_GETFL);
        if (flags < 0)
                return -errno;

        flags &= ~(O_ASYNC | O_NONBLOCK);
        if (fcntl(fd, F_SETFL, flags) < 0)
                return -errno;
        return 0;
}

int os_set_fd_block(int fd, int blocking)
{
        int flags;

        flags = fcntl(fd, F_GETFL);
        if (flags < 0)
                return -errno;

        if (blocking)
                flags &= ~O_NONBLOCK;
        else
                flags |= O_NONBLOCK;

        if (fcntl(fd, F_SETFL, flags) < 0)
                return -errno;

        return 0;
}

int os_accept_connection(int fd)
{
        int new;

        new = accept(fd, NULL, 0);
        if (new < 0)
                return -errno;
        return new;
}

#ifndef SHUT_RD
#define SHUT_RD 0
#endif

#ifndef SHUT_WR
#define SHUT_WR 1
#endif

#ifndef SHUT_RDWR
#define SHUT_RDWR 2
#endif

int os_shutdown_socket(int fd, int r, int w)
{
        int what, err;

        if (r && w)
                what = SHUT_RDWR;
        else if (r)
                what = SHUT_RD;
        else if (w)
                what = SHUT_WR;
        else
                return -EINVAL;

        err = shutdown(fd, what);
        if (err < 0)
                return -errno;
        return 0;
}

/**
 * os_rcv_fd_msg - receive message with (optional) FDs
 * @fd: the FD to receive from
 * @fds: the array for FDs to write to
 * @n_fds: number of FDs to receive (@fds array size)
 * @data: the message buffer
 * @data_len: the size of the message to receive
 *
 * Receive a message with FDs.
 *
 * Returns: the size of the received message, or an error code
 */
ssize_t os_rcv_fd_msg(int fd, int *fds, unsigned int n_fds,
                      void *data, size_t data_len)
{
#define MAX_RCV_FDS     2
        char buf[CMSG_SPACE(sizeof(*fds) * MAX_RCV_FDS)];
        struct cmsghdr *cmsg;
        struct iovec iov = {
                .iov_base = data,
                .iov_len = data_len,
        };
        struct msghdr msg = {
                .msg_iov = &iov,
                .msg_iovlen = 1,
                .msg_control = buf,
                .msg_controllen = CMSG_SPACE(sizeof(*fds) * n_fds),
        };
        int n;

        if (n_fds > MAX_RCV_FDS)
                return -EINVAL;

        n = recvmsg(fd, &msg, 0);
        if (n < 0)
                return -errno;

        cmsg = CMSG_FIRSTHDR(&msg);
        if (!cmsg ||
            cmsg->cmsg_level != SOL_SOCKET ||
            cmsg->cmsg_type != SCM_RIGHTS)
                return n;

        memcpy(fds, CMSG_DATA(cmsg), cmsg->cmsg_len - CMSG_LEN(0));
        return n;
}

int os_create_unix_socket(const char *file, int len, int close_on_exec)
{
        struct sockaddr_un addr;
        int sock, err;

        sock = socket(PF_UNIX, SOCK_DGRAM, 0);
        if (sock < 0)
                return -errno;

        if (close_on_exec) {
                err = os_set_exec_close(sock);
                if (err < 0)
                        printk(UM_KERN_ERR "create_unix_socket : "
                               "close_on_exec failed, err = %d", -err);
        }

        addr.sun_family = AF_UNIX;

        snprintf(addr.sun_path, len, "%s", file);

        err = bind(sock, (struct sockaddr *) &addr, sizeof(addr));
        if (err < 0)
                return -errno;

        return sock;
}

void os_flush_stdout(void)
{
        fflush(stdout);
}

int os_lock_file(int fd, int excl)
{
        int type = excl ? F_WRLCK : F_RDLCK;
        struct flock lock = ((struct flock) { .l_type   = type,
                                              .l_whence = SEEK_SET,
                                              .l_start  = 0,
                                              .l_len    = 0 } );
        int err, save;

        err = fcntl(fd, F_SETLK, &lock);
        if (!err)
                goto out;

        save = -errno;
        err = fcntl(fd, F_GETLK, &lock);
        if (err) {
                err = -errno;
                goto out;
        }

        printk(UM_KERN_ERR "F_SETLK failed, file already locked by pid %d\n",
               lock.l_pid);
        err = save;
 out:
        return err;
}

unsigned os_major(unsigned long long dev)
{
        return major(dev);
}

unsigned os_minor(unsigned long long dev)
{
        return minor(dev);
}

unsigned long long os_makedev(unsigned major, unsigned minor)
{
        return makedev(major, minor);
}

int os_falloc_punch(int fd, unsigned long long offset, int len)
{
        int n = fallocate(fd, FALLOC_FL_PUNCH_HOLE|FALLOC_FL_KEEP_SIZE, offset, len);

        if (n < 0)
                return -errno;
        return n;
}

int os_falloc_zeroes(int fd, unsigned long long offset, int len)
{
        int n = fallocate(fd, FALLOC_FL_ZERO_RANGE|FALLOC_FL_KEEP_SIZE, offset, len);

        if (n < 0)
                return -errno;
        return n;
}

int os_eventfd(unsigned int initval, int flags)
{
        int fd = eventfd(initval, flags);

        if (fd < 0)
                return -errno;
        return fd;
}

int os_sendmsg_fds(int fd, const void *buf, unsigned int len, const int *fds,
                   unsigned int fds_num)
{
        struct iovec iov = {
                .iov_base = (void *) buf,
                .iov_len = len,
        };
        union {
                char control[CMSG_SPACE(sizeof(*fds) * OS_SENDMSG_MAX_FDS)];
                struct cmsghdr align;
        } u;
        unsigned int fds_size = sizeof(*fds) * fds_num;
        struct msghdr msg = {
                .msg_iov = &iov,
                .msg_iovlen = 1,
                .msg_control = u.control,
                .msg_controllen = CMSG_SPACE(fds_size),
        };
        struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
        int err;

        if (fds_num > OS_SENDMSG_MAX_FDS)
                return -EINVAL;
        memset(u.control, 0, sizeof(u.control));
        cmsg->cmsg_level = SOL_SOCKET;
        cmsg->cmsg_type = SCM_RIGHTS;
        cmsg->cmsg_len = CMSG_LEN(fds_size);
        memcpy(CMSG_DATA(cmsg), fds, fds_size);
        err = sendmsg(fd, &msg, 0);

        if (err < 0)
                return -errno;
        return err;
}

int os_poll(unsigned int n, const int *fds)
{
        /* currently need 2 FDs at most so avoid dynamic allocation */
        struct pollfd pollfds[2] = {};
        unsigned int i;
        int ret;

        if (n > ARRAY_SIZE(pollfds))
                return -EINVAL;

        for (i = 0; i < n; i++) {
                pollfds[i].fd = fds[i];
                pollfds[i].events = POLLIN;
        }

        ret = poll(pollfds, n, -1);
        if (ret < 0)
                return -errno;

        /* Return the index of the available FD */
        for (i = 0; i < n; i++) {
                if (pollfds[i].revents)
                        return i;
        }

        return -EIO;
}

void *os_mmap_rw_shared(int fd, size_t size)
{
        void *res = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);

        if (res == MAP_FAILED)
                return NULL;

        return res;
}

void *os_mremap_rw_shared(void *old_addr, size_t old_size, size_t new_size)
{
        void *res;

        res = mremap(old_addr, old_size, new_size, MREMAP_MAYMOVE, NULL);

        if (res == MAP_FAILED)
                return NULL;

        return res;
}