// SPDX-License-Identifier: LGPL-2.1+
/*
 * Copyright (c) 2020, DataDirect Networks Inc, all rights reserved.
 */
/*
 * This file is part of Lustre, http://www.lustre.org/
 *
 * lustreapi library for lseek-related functionality
 *
 * Author: Mikhail Pershin <mpershin@whamcloud.com>
 */

#include <stdlib.h>
#include <unistd.h>
#include <stdbool.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#ifndef FALLOC_FL_PUNCH_HOLE
#include <linux/falloc.h> /* for RHEL7.3 glibc-headers and earlier */
#endif
#include <sys/syscall.h>
#include <sys/ioctl.h>
#include <errno.h>

#include <lustre/lustreapi.h>
#include "lustreapi_internal.h"

/**
 * Check if file has a hole
 *
 * \param fd	file descriptor
 *
 * \retval boolean, true if file has a hole, false otherwise
 */
bool llapi_file_is_sparse(int fd)
{
	off_t file_end, hole_off;

	file_end = lseek(fd, 0, SEEK_END);
	hole_off = lseek(fd, 0, SEEK_HOLE);

	/* Errors are ignored and file is just reported as non-sparse */
	return file_end > 0 && hole_off >= 0 && hole_off < file_end;
}

/**
 * Get the first data segment in given extent.
 *
 * \param src_fd  source file descriptor
 * \param offset  offset to start from
 * \param length  length of data segment found
 *
 * \retval next data offset and length on \p length on success.
 * \retval -errno on failure.
 */
off_t llapi_data_seek(int src_fd, off_t offset, size_t *length)
{
	off_t data_off, hole_off;
	int rc;

	if (offset < 0) {
		rc = -EINVAL;
		llapi_error(LLAPI_MSG_ERROR, rc, "wrong offset: %jd",
			    offset);
		return rc;
	}

	data_off = lseek(src_fd, offset, SEEK_DATA);
	if (data_off < 0) {
		if (errno != ENXIO) {
			rc = -errno;
			llapi_error(LLAPI_MSG_ERROR, rc,
				    "failed SEEK_DATA from %jd",
				    offset);
			return rc;
		}
		hole_off = lseek(src_fd, 0, SEEK_END);
		if (data_off > hole_off) /* out of file range */
			return -ENXIO;
		/* no more data in src file, return end of file and zero size
		 * so caller will know there must be hole up to that offset
		 */
		*length = 0;
		return hole_off;
	}

	hole_off = lseek(src_fd, data_off, SEEK_HOLE);
	if (hole_off < 0) {
		rc = -errno;
		llapi_error(LLAPI_MSG_ERROR, rc,
			    "failed SEEK_HOLE from %jd", data_off);
		return rc;
	}
	*length = hole_off - data_off;
	return data_off;
}

/**
 * Punch hole in a file.
 *
 * \param fd     file descriptor
 * \param start  offset to start from
 * \param length hole length
 *
 * \retval 0 on success.
 * \retval -errno on failure to punch hole
 */
int llapi_hole_punch(int fd, off_t start, size_t length)
{
	int rc;

	rc = fallocate(fd, FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE,
		       start, length);
	if (rc)
		rc = -errno;
	return rc;
}
