// SPDX-License-Identifier: LGPL-2.0-only
// SPDX-FileCopyrightText: 2023 Oleksij Rempel <linux@rempel-privat.de>

#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <time.h>
#include <unistd.h>

#include "isobusfs_srv.h"
#include "isobusfs_cmn_fa.h"

static struct isobusfs_srv_handles *
isobusfs_srv_walk_handles(struct isobusfs_srv_priv *priv, const char *path)
{
	unsigned int i;

	for (i = 0; i < ARRAY_SIZE(priv->handles); i++) {
		if (priv->handles[i].path == NULL)
			continue;

		if (!strcmp(priv->handles[i].path, path))
			return &priv->handles[i];
	}

	return NULL;
}

static int isobusfs_srv_add_file(struct isobusfs_srv_priv *priv,
				 const char *path, int fd, DIR *dir)
{
	unsigned int j;

	if (priv->handles_count >= (int)ARRAY_SIZE(priv->handles)) {
		pr_err("too many handles");
		return -ENOSPC;
	}

	for (j = 0; j < ARRAY_SIZE(priv->handles); j++) {
		if (priv->handles[j].path == NULL)
			break;
	}

	priv->handles[j].path = strdup(path);
	priv->handles[j].fd = fd;
	priv->handles[j].dir = dir;

	priv->handles_count++;
	return j;
}

static int isobusfs_srv_add_client_to_file(struct isobusfs_srv_handles *file,
					   struct isobusfs_srv_client *client)
{
	unsigned int j;

	for (j = 0; j < ARRAY_SIZE(file->clients); j++) {
		if (file->clients[j] == client)
			return 0;
	}

	for (j = 0; j < ARRAY_SIZE(file->clients); j++) {
		if (file->clients[j] == NULL) {
			file->clients[j] = client;
			file->refcount++;
			return 0;
		}
	}

	pr_err("%s: can't add client to file", __func__);
	return -ENOENT;
}

static int isobusfs_srv_request_file(struct isobusfs_srv_priv *priv,
				     struct isobusfs_srv_client *client,
				     const char *path, int fd, DIR *dir)
{
	struct isobusfs_srv_handles *file;
	int handle, ret;

	file = isobusfs_srv_walk_handles(priv, path);
	if (!file) {
		handle = isobusfs_srv_add_file(priv, path, fd, dir);
		if (handle < 0)
			return handle;

		file = &priv->handles[handle];
	} else {
		handle = file - priv->handles;
	}

	ret = isobusfs_srv_add_client_to_file(file, client);
	if (ret < 0)
		return ret;

	return handle;
}

static struct isobusfs_srv_handles *
isobusfs_srv_get_handle(struct isobusfs_srv_priv *priv, int handle)
{
	if (handle < 0 || handle >= (int)ARRAY_SIZE(priv->handles))
		return NULL;

	return &priv->handles[handle];
}

static int isobusfs_srv_release_handle(struct isobusfs_srv_priv *priv,
				       struct isobusfs_srv_client *client,
				       int handle)
{
	struct isobusfs_srv_handles *hdl = isobusfs_srv_get_handle(priv, handle);
	unsigned int client_index;

	if (!hdl) {
		pr_warn("%s: invalid handle %d", __func__, handle);
		return -ENOENT;
	}

	/* Find the client in the hdl's client list and remove it */
	for (client_index = 0; client_index < ARRAY_SIZE(hdl->clients); client_index++) {
		if (hdl->clients[client_index] == client) {
			hdl->clients[client_index] = NULL;
			hdl->refcount--;

			pr_debug("%s: client %p removed from handle %d", __func__, client, handle);
			/* If refcount is 0, close the hdl and remove it from the list */
			if (hdl->refcount == 0) {
				pr_debug("%s: closing handle %d", __func__, handle);
				/* fd will be automatically closed when
				 * closedir(3) is called.
				 */
				if (hdl->dir)
					closedir(hdl->dir);
				else
					close(hdl->fd);
				memset(hdl, 0, sizeof(*hdl));
				priv->handles_count--;
			}

			return 0;
		}
	}

	pr_err("%s: client %p not found in handle %d", __func__, client, handle);
	return -ENOENT;
}

void isobusfs_srv_remove_client_from_handles(struct isobusfs_srv_priv *priv,
					   struct isobusfs_srv_client *client)
{
	unsigned int handle;
	unsigned int client_index;

	for (handle = 0; handle < ARRAY_SIZE(priv->handles); handle++) {
		struct isobusfs_srv_handles *hdl = &priv->handles[handle];

		if (hdl->path == NULL)
			continue;

		for (client_index = 0; client_index < ARRAY_SIZE(hdl->clients); client_index++) {
			if (hdl->clients[client_index] == client) {
				hdl->clients[client_index] = NULL;
				hdl->refcount--;

				if (hdl->refcount == 0) {
					close(hdl->fd);
					memset(hdl, 0, sizeof(*hdl));
					priv->handles_count--;
				}

				break;
			}
		}
	}
}

static int isobusfs_srv_fa_open_directory(struct isobusfs_srv_priv *priv,
					  struct isobusfs_srv_client *client,
					  const char *path, size_t path_len,
					  uint8_t *handle)
{
	char linux_path[ISOBUSFS_SRV_MAX_PATH_LEN];
	struct isobusfs_srv_handles *hdl;
	struct stat file_stat;
	int file_index, fd;
	DIR *dir;
	int ret;

	ret = isobusfs_path_to_linux_path(priv, path, path_len, linux_path, sizeof(linux_path));
	if (ret < 0)
		return ret;

	hdl = isobusfs_srv_walk_handles(priv, linux_path);
	if (hdl) {
		pr_err("%s: Path %s is already opened\n", __func__, linux_path);
		return ISOBUSFS_ERR_OTHER;
	}

	dir = opendir(linux_path);
	if (!dir) {
		pr_err("%s: Error opening directory %s. Error %d (%s)\n",
		       __func__, linux_path, errno, strerror(errno));
		switch (errno) {
		case EACCES:
			return ISOBUSFS_ERR_ACCESS_DENIED;
		case ENOENT:
			return ISOBUSFS_ERR_FILE_ORPATH_NOT_FOUND;
		case ENOMEM:
			return ISOBUSFS_ERR_OUT_OF_MEM;
		default:
			return ISOBUSFS_ERR_OTHER;
		}
	}

	fd = dirfd(dir);
	if (fd < 0) {
		pr_err("%s: Error getting file descriptor for directory %s. Error %d (%s)\n",
		       __func__, linux_path, errno, strerror(errno));
		closedir(dir);
		return ISOBUSFS_ERR_OTHER;
	}

	if (fstat(fd, &file_stat) < 0 || !S_ISDIR(file_stat.st_mode)) {
		pr_err("%s: Path %s is not a directory\n", __func__,
		       linux_path);
		closedir(dir);
		return ISOBUSFS_ERR_INVALID_ACCESS;
	}

	file_index = isobusfs_srv_request_file(priv, client, linux_path, fd,
					       dir);
	if (file_index < 0) {
		closedir(dir);
		return file_index;
	}

	*handle = (uint8_t)file_index;

	return 0;
}

static int isobusfs_srv_fa_open_file(struct isobusfs_srv_priv *priv,
				     struct isobusfs_srv_client *client,
				     const char *path, size_t path_len,
				     uint8_t flags, uint8_t *handle)
{
	char linux_path[ISOBUSFS_SRV_MAX_PATH_LEN];
	struct isobusfs_srv_handles *hdl;
	struct stat file_stat;
	int open_flags = 0;
	int file_index;
	int ret, fd;

	ret = isobusfs_path_to_linux_path(priv, path, path_len,
					  linux_path, sizeof(linux_path));
	if (ret < 0)
		return ret;

	pr_debug("convert ISOBUS FS path to linux path: %.*s -> %s",
		 path_len, path, linux_path);

	/* Determine open flags based on the requested access type */
	switch (flags & ISOBUSFS_FA_OPEN_MASK) {
	case ISOBUSFS_FA_OPEN_FILE_RO:
		open_flags |= O_RDONLY;
		break;
	case ISOBUSFS_FA_OPEN_FILE_WO:
		open_flags |= O_WRONLY;
		break;
	case ISOBUSFS_FA_OPEN_FILE_WR:
		open_flags |= O_RDWR;
		if (!(flags & ISOBUSFS_FA_OPEN_APPEND))
			open_flags |= O_TRUNC;
		break;
	default:
		return ISOBUSFS_ERR_INVALID_ACCESS;
	}

	if (flags & ISOBUSFS_FA_OPEN_APPEND)
		open_flags |= O_APPEND;

	/* Check if the file is already opened */
	hdl = isobusfs_srv_walk_handles(priv, linux_path);
	if (hdl) {
		pr_warn("Handle: %s is already opened by client: %x\n",
			linux_path, client->addr);
		fd = hdl->fd;
	} else {
		/* Open the file if not already opened */
		fd = open(linux_path, open_flags);
		if (fd < 0) {
			switch (errno) {
			case EACCES:
				return ISOBUSFS_ERR_ACCESS_DENIED;
			case EINVAL:
				return ISOBUSFS_ERR_INVALID_ACCESS;
			case EMFILE:
			case ENFILE:
				return ISOBUSFS_ERR_TOO_MANY_FILES_OPEN;
			case ENOENT:
				return ISOBUSFS_ERR_FILE_ORPATH_NOT_FOUND;
			case ENOMEM:
				return ISOBUSFS_ERR_OUT_OF_MEM;
			default:
				return ISOBUSFS_ERR_OTHER;
			}
		}
		/* Check if the opened path is a regular file */
		if (fstat(fd, &file_stat) < 0) {
			close(fd);
			return ISOBUSFS_ERR_OTHER;
		}

		if (!S_ISREG(file_stat.st_mode)) {
			close(fd);
			/* Invalid access (not a regular file) */
			return ISOBUSFS_ERR_INVALID_ACCESS;
		}
	}

	/* Request the file, which also handles refcount and client list 
	 * updates
	 */
	file_index = isobusfs_srv_request_file(priv, client, linux_path, fd,
					       NULL);
	if (file_index < 0) {
		close(fd);
		return file_index;
	}

	*handle = (uint8_t)file_index;

	return 0;
}

static int isobusfs_srv_fa_open_file_req(struct isobusfs_srv_priv *priv,
					 struct isobusfs_msg *msg)
{
	struct isobusfs_fa_openf_req *req =
		(struct isobusfs_fa_openf_req *)msg->buf;
	uint16_t name_len = le16toh(req->name_len);
	struct isobusfs_srv_client *client;
	struct isobusfs_fa_openf_res res;
	uint8_t error_code = 0;
	uint8_t access_type;
	size_t abs_path_len;
	uint8_t handle = 0;
	char *abs_path;
	int ret = 0;

	client = isobusfs_srv_get_client_by_msg(priv, msg);
	if (!client) {
		pr_warn("client not found");
		error_code = ISOBUSFS_ERR_OTHER;
		goto send_response;
	}

	if (name_len > msg->len - sizeof(*req)) {
		error_code = ISOBUSFS_ERR_INVALID_ACCESS;
		goto send_response;
	}

	/* Perform checks on the received request, e.g., validate path length */
	if (name_len > ISOBUSFS_MAX_PATH_NAME_LENGTH) {
		error_code = ISOBUSFS_ERR_INVALID_ACCESS;
		goto send_response;
	}

	abs_path_len = ISOBUSFS_SRV_MAX_PATH_LEN;
	abs_path = malloc(abs_path_len);
	if (!abs_path) {
		pr_warn("failed to allocate memory");
		return -ENOMEM;
	}

	if (client->current_dir[0] == '\0')
		isobusfs_srv_set_default_current_dir(priv, client);

	/* Normalize provided string and convert it to absolute ISOBUS FS path */
	ret = isobusfs_convert_relative_to_absolute(priv, client->current_dir,
						    (char *)req->name, req->name_len,
						    abs_path, abs_path_len);
	if (ret < 0) {
		error_code = ISOBUSFS_ERR_FILE_ORPATH_NOT_FOUND;
		goto send_response;
	}

	pr_debug("< rx: Open File Request. from client 0x%2x: %.*s. Current directory: %s",
		 client->addr, req->name_len, req->name, client->current_dir);

	access_type = FIELD_GET(ISOBUSFS_FA_OPEN_MASK, req->flags);
	if (access_type == ISOBUSFS_FA_OPEN_DIR) {
		error_code = isobusfs_srv_fa_open_directory(priv, client, abs_path,
							    abs_path_len, &handle);
	} else {
		error_code = isobusfs_srv_fa_open_file(priv, client, abs_path,
						       abs_path_len, req->flags,
						       &handle);
	}

send_response:
	res.fs_function =
		isobusfs_cg_function_to_buf(ISOBUSFS_CG_FILE_ACCESS,
					    ISOBUSFS_FA_F_OPEN_FILE_RES);
	res.tan = req->tan;
	res.error_code = error_code;
	res.handle = handle;
	memset(&res.reserved[0], 0xff, sizeof(res.reserved));

	/* send to socket */
	ret = isobusfs_srv_sendto(priv, msg, &res, sizeof(res));
	if (ret < 0) {
		pr_warn("can't send current directory response");
		goto err;
	}

	pr_debug("> tx: Open File Response. Error code: %d (%s).", error_code,
		 isobusfs_error_to_str(error_code));
err:

	return ret;
}

static int isobusfs_srv_read_file(struct isobusfs_srv_handles *handle,
				  uint8_t *buffer, size_t count,
				  ssize_t *readed_size)
{
	*readed_size = read(handle->fd, buffer, count);
	if (*readed_size < 0) {
		switch (errno) {
		case EBADF:
			return ISOBUSFS_ERR_INVALID_HANDLE;
		case EFAULT:
			return ISOBUSFS_ERR_OUT_OF_MEM;
		case EIO:
			return ISOBUSFS_ERR_ON_READ;
		default:
			return ISOBUSFS_ERR_OTHER;
		}
	}

	return 0;
}

static uint16_t convert_to_file_date(time_t time_val)
{
	struct tm *timeinfo = localtime(&time_val);
	int year, month, day;

	if (!timeinfo)
		return 0;

	year = timeinfo->tm_year + 1900 - 1980;
	month = timeinfo->tm_mon + 1;
	day = timeinfo->tm_mday;

	if (year < 0 || year > 127)
		return 0;

	return (year << 9) | (month << 5) | day;
}

static uint16_t convert_to_file_time(time_t time_val)
{
	struct tm *timeinfo = localtime(&time_val);
	int hours, minutes, seconds;
	uint16_t time;

	if (!timeinfo)
		return 0;

	hours = timeinfo->tm_hour;
	minutes = timeinfo->tm_min;
	seconds = timeinfo->tm_sec / 2;

	time = (hours << 11) | (minutes << 5) | seconds;

	return time;
}

static int check_access_with_base(const char *base_dir,
				  const char *relative_path, int mode)
{
	char full_path[ISOBUSFS_SRV_MAX_PATH_LEN];

	if (snprintf(full_path, sizeof(full_path), "%s/%s", base_dir,
		     relative_path) >= (int)sizeof(full_path)) {
		return -ENAMETOOLONG;
	}

	return access(full_path, mode);
}

static int isobusfs_srv_read_directory(struct isobusfs_srv_handles *handle,
				       uint8_t *buffer, size_t count,
				       ssize_t *readed_size)
{
	DIR *dir = handle->dir;
	struct dirent *entry;
	size_t pos = 0;

	/*
	 * Position the directory stream to the previously stored offset (handle->dir_pos).
	 *
	 * Handling Changes in Directory Contents:
	 * - If the directory contents change between reads (e.g., files/directories added or deleted),
	 *   handle->dir_pos may not point to the expected entry.
	 * - To ensure consistency, implement checks (e.g., compare inode numbers) to verify the correct
	 *   entry position.
	 *
	 * Detecting End of Directory:
	 * - If readdir() returns NULL before reaching handle->dir_pos, it indicates the end of the
	 *   directory with no more entries to read.
	 * - This situation should be handled appropriately, such as by resetting handle->dir_pos and
	 *   either returning an error or restarting from the beginning of the directory, depending
	 *   on the application's requirements.
	 */
	for (int i = 0; i < handle->dir_pos &&
	     (entry = readdir(dir)) != NULL; i++) {
		/* Iterating to the desired position */
	}

	/*
	 * Directory Entry Layout:
	 * This loop reads directory entries and encodes them into a buffer.
	 * Each entry in the buffer follows the format specified in ISO 11783-13:2021.
	 *
	 * The layout of each directory entry in the buffer is as follows:
	 * - Byte 1: Filename Length (as per ISO 11783-13:2021 B.22).
	 *   Represents the length of the filename that follows.
	 *
	 * - Byte 2–n: Filename (as per ISO 11783-13:2021 B.23).
	 *   The actual name of the file or directory.
	 *
	 * - Byte n + 1: Attributes (as per ISO 11783-13:2021 B.15).
	 *
	 * - Bytes n + 2, n + 3: File Date (as per ISO 11783-13:2021 B.24).
	 *   Encoded file date, using a 16-bit format derived from file_stat.st_mtime.
	 *
	 * - Bytes n + 4, n + 5: File Time (as per ISO 11783-13:2021 B.25).
	 *   Encoded file time, using a 16-bit format derived from file_stat.st_mtime.
	 *
	 * - Bytes n + 6 … n + 9: Size (as per ISO 11783-13:2021 B.26).
	 *   The size of the file in bytes, encoded in a 32-bit little-endian format.
	 *
	 * The handle->dir_pos is incremented after processing each entry, marking
	 * the current position in the directory stream for subsequent reads.
	 */
	while ((entry = readdir(dir)) != NULL) {
		size_t entry_name_len, entry_total_len;
		__le16 file_date, file_time;
		uint8_t attributes = 0;
		struct stat file_stat;
		__le32 size;

		if (check_access_with_base(handle->path, entry->d_name, R_OK) != 0)
			continue; /* Skip this entry if it's not readable */

		if (fstatat(handle->fd, entry->d_name, &file_stat, 0) < 0)
			continue; /* Skip this entry on error */

		entry_name_len = strlen(entry->d_name);
		if (entry_name_len > ISOBUSFS_MAX_DIR_ENTRY_NAME_LENGTH)
			continue;

		entry_total_len = 1 + entry_name_len + 1 + 2 + 2 + 4;

		if (pos + entry_total_len > count)
			break;

		buffer[pos++] = (uint8_t)entry_name_len;

		memcpy(buffer + pos, entry->d_name, entry_name_len);
		pos += entry_name_len;

		if (S_ISDIR(file_stat.st_mode))
			attributes |= ISOBUSFS_ATTR_DIRECTORY;
		if (check_access_with_base(handle->path, entry->d_name, W_OK) != 0)
			attributes |= ISOBUSFS_ATTR_READ_ONLY;
		buffer[pos++] = attributes;

		file_date = htole16(convert_to_file_date(file_stat.st_mtime));
		memcpy(buffer + pos, &file_date, sizeof(file_date));
		pos += sizeof(file_date);

		file_time = htole16(convert_to_file_time(file_stat.st_mtime));
		memcpy(buffer + pos, &file_time, sizeof(file_time));
		pos += sizeof(file_time);

		size = htole32(file_stat.st_size);
		memcpy(buffer + pos, &size, sizeof(size));
		pos += sizeof(size);
	}

	*readed_size = pos;
	return 0;
}

static int isobusfs_srv_fa_rf_req(struct isobusfs_srv_priv *priv,
				  struct isobusfs_msg *msg)
{
	uint8_t res_fail[8] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
	struct isobusfs_read_file_response *res;
	struct isobusfs_srv_handles *handle;
	struct isobusfs_srv_client *client;
	struct isobusfs_fa_readf_req *req;
	ssize_t readed_size = 0;
	uint8_t error_code = 0;
	ssize_t send_size;
	int ret = 0;
	int count;

	req = (struct isobusfs_fa_readf_req *)msg->buf;
	count = le16toh(req->count);

	pr_debug("< rx: Read File Request. tan: %d, handle: %d, count: %d",
		 req->tan, req->handle, count);
	/* C.3.5.1 Read File, General:
	 * The requested data (excluding the other parameters) is sent in
	 * the response (up to 1 780 bytes when TP is used, up to 65 530 bytes
	 * when ETP is used). The number of data bytes read can be less than
	 * requested if the end of the file is reached.
	 * TODO: currently we are not able to detect support transport mode,
	 * so ETP is assumed.
	 */
	if (count > ISOBUSFS_MAX_DATA_LENGH)
		count = ISOBUSFS_MAX_DATA_LENGH;

	res = malloc(sizeof(*res) + count);
	if (!res) {
		pr_warn("failed to allocate memory");
		res = (struct isobusfs_read_file_response *)&res_fail[0];
		error_code = ISOBUSFS_ERR_OUT_OF_MEM;
		goto send_response;
	}

	client = isobusfs_srv_get_client_by_msg(priv, msg);
	if (!client) {
		pr_warn("client not found");
		error_code = ISOBUSFS_ERR_OTHER;
		goto send_response;
	}

	handle = isobusfs_srv_get_handle(priv, req->handle);
	if (!handle) {
		pr_warn("failed to find file with handle: %x", req->handle);
		error_code = ISOBUSFS_ERR_FILE_ORPATH_NOT_FOUND;
	}

	/* Determine whether to read a file or a directory */
	if (handle->dir) {
		ret = isobusfs_srv_read_directory(handle, res->data, count,
						  &readed_size);
	} else {
		ret = isobusfs_srv_read_file(handle, res->data, count,
					     &readed_size);
	}

	if (ret < 0) {
		error_code = ret;
		readed_size = 0;
	} else if (count != 0 && readed_size == 0) {
		error_code = ISOBUSFS_ERR_END_OF_FILE;
	}

send_response:
	res->fs_function =
		isobusfs_cg_function_to_buf(ISOBUSFS_CG_FILE_ACCESS,
					    ISOBUSFS_FA_F_READ_FILE_RES);
	res->tan = req->tan;
	res->error_code = error_code;
	res->count = htole16(readed_size);

	send_size = sizeof(*res) + readed_size;
	if (send_size < ISOBUSFS_MIN_TRANSFER_LENGH)
		send_size = ISOBUSFS_MIN_TRANSFER_LENGH;

	/* send to socket */
	ret = isobusfs_srv_sendto(priv, msg, res, send_size);
	if (ret < 0) {
		pr_warn("can't send Read File Response");
		goto free_res;
	}

	pr_debug("> tx: Read File Response. Error code: %d (%s), readed size: %d",
		 error_code, isobusfs_error_to_str(error_code), readed_size);

free_res:
	free(res);
	return ret;
}

static int isobusfs_srv_seek(struct isobusfs_srv_priv *priv,
			     struct isobusfs_srv_handles *handle, int32_t offset,
			     uint8_t position_mode)
{
	int whence;
	off_t offs;

	switch (position_mode) {
	case ISOBUSFS_FA_SEEK_SET:
		whence = SEEK_SET;
		if (offset < 0) {
			pr_warn("Invalid offset. Offset must be positive.");
			return ISOBUSFS_ERR_INVALID_REQUESTED_LENGHT;
		}
		break;
	case ISOBUSFS_FA_SEEK_CUR:
		whence = SEEK_CUR;
		if (offset < 0 && handle->offset < -offset) {
			pr_warn("Invalid offset. Negative offset is too big.");
			return ISOBUSFS_ERR_INVALID_REQUESTED_LENGHT;
		}
		break;
	case ISOBUSFS_FA_SEEK_END:
		whence = SEEK_END;
		if (offset > 0) {
			pr_warn("Invalid offset. Offset must be negative");
			return ISOBUSFS_ERR_INVALID_REQUESTED_LENGHT;
		}
		break;
	default:
		pr_warn("invalid position mode");
		return ISOBUSFS_ERR_OTHER;
	}

	/* seek file */
	offs = lseek(handle->fd, offset, whence);
	if (offs < 0) {
		pr_warn("Failed to seek file");

		switch (offs) {
		case EBADF:
			return ISOBUSFS_ERR_INVALID_HANDLE;
		case EINVAL:
			return ISOBUSFS_ERR_INVALID_REQUESTED_LENGHT;
		case ENXIO:
			return ISOBUSFS_ERR_END_OF_FILE;
		case EOVERFLOW:
			return ISOBUSFS_ERR_OUT_OF_MEM;
		case ESPIPE:
			return ISOBUSFS_ERR_ACCESS_DENIED;
		default:
			return ISOBUSFS_ERR_OTHER;
		}
	}

	handle->offset = offs;

	return ISOBUSFS_ERR_SUCCESS;
}

static int isobusfs_srv_seek_directory(struct isobusfs_srv_handles *handle,
				       int32_t offset)
{
	DIR *dir = fdopendir(handle->fd);

	if (!dir)
		return ISOBUSFS_ERR_OTHER;

	rewinddir(dir);

	for (int32_t i = 0; i < offset; i++) {
		if (readdir(dir) == NULL)
			return ISOBUSFS_ERR_END_OF_FILE;
	}

	handle->dir_pos = offset;

	return ISOBUSFS_ERR_SUCCESS;
}

static int isobusfs_srv_fa_sf_req(struct isobusfs_srv_priv *priv,
				  struct isobusfs_msg *msg)
{
	struct isobusfs_fa_seekf_res res = {0};
	struct isobusfs_srv_client *client;
	struct isobusfs_fa_seekf_req *req;
	struct isobusfs_srv_handles *handle;
	int32_t offset_out = 0;
	uint8_t error_code = 0;
	int ret;

	req = (struct isobusfs_fa_seekf_req *)msg->buf;
	pr_debug("< rx: Seek File Request. Handle: %x, offset: %d, position mode: %d",
		 req->handle, le32toh(req->offset), req->position_mode);

	client = isobusfs_srv_get_client_by_msg(priv, msg);
	if (!client) {
		pr_warn("client not found");
		error_code = ISOBUSFS_ERR_OTHER;
		goto send_response;
	}

	handle = isobusfs_srv_get_handle(priv, req->handle);
	if (!handle) {
		pr_warn("failed to find handle: %x", req->handle);
		error_code = ISOBUSFS_ERR_INVALID_HANDLE;
		goto send_response;
	}

	if (handle->dir) {
		error_code = isobusfs_srv_seek_directory(handle,
							 le32toh(req->offset));
		res.position = htole32(handle->dir_pos);
	} else {
		error_code = isobusfs_srv_seek(priv, handle, le32toh(req->offset),
					       req->position_mode);
		res.position = htole32(handle->offset);
	}

send_response:
	res.fs_function =
		isobusfs_cg_function_to_buf(ISOBUSFS_CG_FILE_ACCESS,
					    ISOBUSFS_FA_F_SEEK_FILE_RES);
	res.tan = req->tan;
	res.error_code = error_code;

	/* send to socket */
	ret = isobusfs_srv_sendto(priv, msg, &res, sizeof(res));
	if (ret < 0) {
		pr_warn("can't send seek file response");
		return ret;
	}

	pr_debug("> tx: Seek File Response. Error code: %d, offset: %d",
		 error_code, offset_out);

	return 0;
}

static int isobusfs_srv_fa_cf_req(struct isobusfs_srv_priv *priv,
				  struct isobusfs_msg *msg)
{
	struct isobusfs_close_file_request *req;
	struct isobusfs_close_file_res res;
	struct isobusfs_srv_client *client;
	uint8_t error_code = 0;
	int ret;

	req = (struct isobusfs_close_file_request *)msg->buf;

	client = isobusfs_srv_get_client_by_msg(priv, msg);
	if (!client) {
		pr_warn("client not found");
		error_code = ISOBUSFS_ERR_OTHER;
		goto send_response;
	}

	ret = isobusfs_srv_release_handle(priv, client, req->handle);
	if (ret < 0) {
		pr_warn("failed to release handle: %x", req->handle);
		switch (ret) {
		case -ENOENT:
			error_code = ISOBUSFS_ERR_FILE_ORPATH_NOT_FOUND;
			break;
		default:
			error_code = ISOBUSFS_ERR_OTHER;
		}
	}

send_response:
	res.fs_function =
		isobusfs_cg_function_to_buf(ISOBUSFS_CG_FILE_ACCESS,
					    ISOBUSFS_FA_F_CLOSE_FILE_RES);
	res.tan = req->tan;
	res.error_code = error_code;
	memset(&res.reserved[0], 0xff, sizeof(res.reserved));

	/* send to socket */
	ret = isobusfs_srv_sendto(priv, msg, &res, sizeof(res));
	if (ret < 0) {
		pr_warn("can't send current directory response");
		goto err;
	}

	pr_debug("> tx: Close File Response. Error code: %d", error_code);

err:
	return ret;
}

/* Command group: file access */
int isobusfs_srv_rx_cg_fa(struct isobusfs_srv_priv *priv,
			  struct isobusfs_msg *msg)
{
	int func = isobusfs_buf_to_function(msg->buf);
	int ret = 0;

	switch (func) {
	case ISOBUSFS_FA_F_OPEN_FILE_REQ:
		ret = isobusfs_srv_fa_open_file_req(priv, msg);
		break;
	case ISOBUSFS_FA_F_CLOSE_FILE_REQ:
		ret = isobusfs_srv_fa_cf_req(priv, msg);
		break;
	case ISOBUSFS_FA_F_READ_FILE_REQ:
		ret = isobusfs_srv_fa_rf_req(priv, msg);
		break;
	case ISOBUSFS_FA_F_SEEK_FILE_REQ:
		ret = isobusfs_srv_fa_sf_req(priv, msg);
		break;
	case ISOBUSFS_FA_F_WRITE_FILE_REQ: /* fall through */
	default:
		pr_warn("%s: unsupported function: %i", __func__, func);
		isobusfs_srv_send_error(priv, msg,
					ISOBUSFS_ERR_FUNC_NOT_SUPPORTED);
	}

	return ret;
}
