/*
 * Copyright (c) 2009, 2010 Jeff Mahoney <jeffm@suse.com>
 * Copyright (c) 2011-2016 Dmitry V. Levin <ldv@strace.io>
 * Copyright (c) 2016-2021 The strace developers.
 * All rights reserved.
 *
 * SPDX-License-Identifier: LGPL-2.1-or-later
 */

#include "defs.h"

#include DEF_MPERS_TYPE(struct_hd_geometry)

#include <linux/hdreg.h>

typedef struct hd_geometry struct_hd_geometry;

#include MPERS_DEFS

#include "xlat/hdio_drive_cmds.h"

#include "gen/generated.h"

static int
print_hdio_getgeo(struct tcb *const tcp, const kernel_ulong_t arg)
{
	if (entering(tcp)) {
		tprint_arg_next();

		return 0;
	}

	/* exiting */
	struct_hd_geometry geo;

	if (umove_or_printaddr(tcp, arg, &geo))
		return RVAL_IOCTL_DECODED;

	tprint_struct_begin();
	PRINT_FIELD_U(geo, heads);
	tprint_struct_next();
	PRINT_FIELD_U(geo, sectors);
	tprint_struct_next();
	PRINT_FIELD_U(geo, cylinders);
	tprint_struct_next();
	PRINT_FIELD_U(geo, start);
	tprint_struct_end();

	return RVAL_IOCTL_DECODED;
}

static int
print_hdio_drive_cmd(struct tcb *const tcp, const kernel_ulong_t arg)
{
	enum { SECTOR_SIZE = 512 };

	if (entering(tcp)) {
		tprint_arg_next();

		struct hd_drive_cmd_hdr c;
		if (umove_or_printaddr(tcp, arg, &c))
			return RVAL_IOCTL_DECODED;

		tprint_struct_begin();
		PRINT_FIELD_XVAL(c, command, hdio_drive_cmds,
				 "ATA_CMD_???");
		tprint_struct_next();
		PRINT_FIELD_U(c, sector_number);
		tprint_struct_next();
		PRINT_FIELD_U(c, feature);
		tprint_struct_next();
		PRINT_FIELD_U(c, sector_count);
		tprint_struct_end();

		return 0;
	}

	/* exiting */
	struct {
		uint8_t status;
		uint8_t error;
		uint8_t nsector;
		uint8_t sector_count;
	} c;

	if ((syserror(tcp) && tcp->u_error != EIO) || umove(tcp, arg, &c))
		return RVAL_IOCTL_DECODED;

	tprint_value_changed();
	tprint_struct_begin();
	PRINT_FIELD_X(c, status);
	tprint_struct_next();
	PRINT_FIELD_U(c, error);
	tprint_struct_next();
	PRINT_FIELD_U(c, nsector);

	if (c.sector_count) {
		tprint_struct_next();
		tprints_field_name("buf");
		printstr_ex(tcp, arg + 4, c.sector_count * SECTOR_SIZE,
			    QUOTE_FORCE_HEX);
	}

	tprint_struct_end();

	return RVAL_IOCTL_DECODED;
}

MPERS_PRINTER_DECL(int, hdio_ioctl, struct tcb *const tcp,
		   const unsigned int code, const kernel_ulong_t arg)
{
	switch (code) {
	case HDIO_GETGEO:
		return print_hdio_getgeo(tcp, arg);
	case HDIO_DRIVE_CMD:
		return print_hdio_drive_cmd(tcp, arg);
	default:
		if (current_klongsize == sizeof(kernel_ulong_t)) {
			return var_ioctl_HDIO(tcp, code, arg);
		} else {
			/*
			 * HDIO compat has never been supported by the kernel.
			 */
			return RVAL_DECODED;
		}
	}

	return RVAL_IOCTL_DECODED;
}