/******************************************************************************
 * Copyright (c) Huawei Technologies Co., Ltd. 2020-2021. All rights reserved.
 * userswap licensed under the Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *     http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
 * PURPOSE.
 * See the Mulan PSL v2 for more details.
 * Author: liuyongqiang
 * Create: 2020-11-06
 * Description: userswap server definition.
 ******************************************************************************/

#define _GNU_SOURCE
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/wait.h>
#include "uswap_log.h"
#include "uswap_server.h"

#define MAX_LISTENQ_NUM 1
#define RESP_MSG_MAX_LEN 10
#define PATH_MAX_LEN 127


int init_socket(void)
{
    int socket_fd;
    int addrlen;
    struct sockaddr_un addr;
    char abstract_path[PATH_MAX_LEN] = {0};
    pid_t pid = getpid();

    socket_fd = socket(AF_LOCAL, SOCK_STREAM, 0);
    if (socket_fd < 0) {
        uswap_log(USWAP_LOG_ERR, "create socket failed\n");
        return -EINVAL;
    }

    bzero(&addr, sizeof(struct sockaddr_un));
    addr.sun_family = AF_UNIX;
    addr.sun_path[0] = 0;
    snprintf(abstract_path, sizeof(abstract_path), "userswap%d.sock", pid);
    memcpy(addr.sun_path + 1, abstract_path, strlen(abstract_path) + 1);
    addrlen = sizeof(addr.sun_family) + strlen(abstract_path) + 1;
    if (bind(socket_fd, (struct sockaddr *)&addr, addrlen) < 0) {
        uswap_log(USWAP_LOG_ERR, "bind socket failed\n");
        close(socket_fd);
        return -EINVAL;
    }

    listen(socket_fd, MAX_LISTENQ_NUM);

    return socket_fd;
}

static int check_socket_permission(int fd, int client_fd)
{
    struct ucred local_cred, client_cred;
    socklen_t len;
    int ret;

    len = sizeof(struct ucred);
    ret = getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &local_cred, &len);
    if (ret < 0) {
        uswap_log(USWAP_LOG_ERR, "get server sockopt failed\n");
        return ret;
    }
    ret = getsockopt(client_fd, SOL_SOCKET, SO_PEERCRED, &client_cred, &len);
    if (ret < 0) {
        uswap_log(USWAP_LOG_ERR, "get client sockopt failed\n");
        return ret;
    }

    if (local_cred.uid != client_cred.uid ||
        local_cred.gid != client_cred.gid) {
        return -EPERM;
    }
    return 0;
}

int sock_handle_rec(int fd, struct swap_vma *swap_vma)
{
    int client_fd;
    int readbytes;
    struct sockaddr_un  clientun;
    socklen_t clientun_len = sizeof(clientun);

    client_fd = accept(fd, (struct sockaddr *)&clientun, &clientun_len);
    if (client_fd < 0) {
        return -EINVAL;
    }

    if (check_socket_permission(fd, client_fd) < 0) {
        close(client_fd);
        return -EPERM;
    }

    readbytes = read(client_fd, swap_vma, sizeof(struct swap_vma));
    if (readbytes <= 0) {
        close(client_fd);
        return -EINVAL;
    }

    return client_fd;
}

int sock_handle_respond(int client_fd, int result)
{
    int writebytes;
    char buff[RESP_MSG_MAX_LEN] = {0};

    if (client_fd < 0) {
        return -EINVAL;
    }

    if (result == 0) {
        snprintf(buff, sizeof(buff), "success");
    } else {
        snprintf(buff, sizeof(buff), "failed");
    }

    writebytes = write(client_fd, buff, (strlen(buff) + 1));
    if (writebytes != (strlen(buff) + 1)) {
        close(client_fd);
        return -EIO;
    }

    close(client_fd);
    return 0;
}