// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * Copyright (c) 2023 FUJITSU LIMITED. All rights reserved.
 * Author: Yang Xu <xuyang2018.jy@fujitsu.com>
 */

/*\
 * [Description]
 *
 * It is a basic test for STATX_DIOALIGN mask on block device.
 *
 * - STATX_DIOALIGN   Want stx_dio_mem_align and stx_dio_offset_align value
 *
 * These two values are tightly coupled to the kernel's current DIO
 * restrictions on block devices.
 *
 * Minimum Linux version required is v6.1.
 */

#define _GNU_SOURCE
#include <sys/types.h>
#include <sys/mount.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdbool.h>
#include <stdio.h>
#include "tst_test.h"
#include "lapi/stat.h"

static char sys_bdev_dma_path[1024], sys_bdev_logical_path[1024];

static void verify_statx(void)
{
	struct statx buf;

	TST_EXP_PASS_SILENT(statx(AT_FDCWD, tst_device->dev, 0, STATX_DIOALIGN, &buf),
		"statx(AT_FDCWD, %s, 0, STATX_DIOALIGN, &buf)", tst_device->dev);

	if (!(buf.stx_mask & STATX_DIOALIGN)) {
		tst_res(TCONF, "Filesystem does not support STATX_DIOALIGN");
		return;
	}

#ifdef HAVE_STRUCT_STATX_STX_DIO_MEM_ALIGN
	/*
	 * This test is tightly coupled to the kernel's current DIO restrictions
	 * on block devices. The general rule of DIO needing to be aligned to the
	 * block device's logical block size was relaxed to allow user buffers
	 * (but not file offsets) aligned to the DMA alignment instead. See v6.0
	 * commit bf8d08532bc1 ("iomap: add support for dma aligned direct-io") and
	 * they are subject to further change in the future.
	 * Also can see commit 2d985f8c6b9 ("vfs: support STATX_DIOALIGN on block devices).
	 */
	TST_ASSERT_ULONG(sys_bdev_dma_path, buf.stx_dio_mem_align - 1);
	TST_ASSERT_ULONG(sys_bdev_logical_path, buf.stx_dio_offset_align);
#else
	tst_res(TCONF, "glibc statx struct miss stx_dio_mem_align field");
#endif
}

static void setup(void)
{
	char full_name[256];
	char *dev_name;

	strcpy(full_name, tst_device->dev);
	dev_name = SAFE_BASENAME(full_name);
	sprintf(sys_bdev_logical_path, "/sys/block/%s/queue/logical_block_size", dev_name);

	/*
	 * Since /sys/block/%s/queue doesn't exist for partition, we need to
	 * use a while to search block device instead of partition.
	 */
	while (access(sys_bdev_logical_path, F_OK) != 0) {
		dev_name[strlen(dev_name)-1] = '\0';
		sprintf(sys_bdev_logical_path, "/sys/block/%s/queue/logical_block_size", dev_name);
	}

	sprintf(sys_bdev_dma_path, "/sys/block/%s/queue/dma_alignment", dev_name);
	if (access(sys_bdev_dma_path, F_OK) != 0)
		tst_brk(TCONF, "dma_alignment sysfs file doesn't exist");
}

static struct tst_test test = {
	.test_all = verify_statx,
	.setup = setup,
	.needs_device = 1,
	.needs_root = 1,
};
