text: // SPDX-License-Identifier: GPL-2.0
  /*
   * ioctl32.c: Conversion between 32bit and 64bit native ioctls.
   *
   * Copyright (C) 1997-2000  Jakub Jelinek  (jakub@redhat.com)
   * Copyright (C) 1998  Eddie C. Dost  (ecd@skynet.be)
   * Copyright (C) 2001,2002  Andi Kleen, SuSE Labs 
   * Copyright (C) 2003       Pavel Machek (pavel@ucw.cz)
   *
   * These routines maintain argument size conversion between 32bit and 64bit
   * ioctls.
   */
  
  #include <linux/types.h>
  #include <linux/compat.h>
  #include <linux/kernel.h>
  #include <linux/capability.h>
  #include <linux/compiler.h>
  #include <linux/sched.h>
  #include <linux/smp.h>
  #include <linux/ioctl.h>
  #include <linux/if.h>
  #include <linux/raid/md_u.h>
  #include <linux/falloc.h>
  #include <linux/file.h>
  #include <linux/ppp-ioctl.h>
  #include <linux/if_pppox.h>
  #include <linux/tty.h>
  #include <linux/vt_kern.h>
  #include <linux/blkdev.h>
  #include <linux/serial.h>
  #include <linux/ctype.h>
  #include <linux/syscalls.h>
  #include <linux/gfp.h>
  #include <linux/cec.h>
  
  #include "internal.h"
  
  #ifdef CONFIG_BLOCK
  #include <linux/cdrom.h>
  #include <linux/fd.h>
  #include <scsi/scsi.h>
  #include <scsi/scsi_ioctl.h>
  #include <scsi/sg.h>
  #endif
  
  #include <linux/uaccess.h>
  #include <linux/watchdog.h>
  
  #include <linux/hiddev.h>
  
  
  #include <linux/sort.h>
  
  /*
   * simple reversible transform to make our table more evenly
   * distributed after sorting.
   */
  #define XFORM(i) (((i) ^ ((i) << 27) ^ ((i) << 17)) & 0xffffffff)
  
  #define COMPATIBLE_IOCTL(cmd) XFORM((u32)cmd),
  static unsigned int ioctl_pointer[] = {
  #ifdef CONFIG_BLOCK
  /* Big S */
  COMPATIBLE_IOCTL(SCSI_IOCTL_GET_IDLUN)
  COMPATIBLE_IOCTL(SCSI_IOCTL_DOORLOCK)
  COMPATIBLE_IOCTL(SCSI_IOCTL_DOORUNLOCK)
  COMPATIBLE_IOCTL(SCSI_IOCTL_TEST_UNIT_READY)
  COMPATIBLE_IOCTL(SCSI_IOCTL_GET_BUS_NUMBER)
  COMPATIBLE_IOCTL(SCSI_IOCTL_SEND_COMMAND)
  COMPATIBLE_IOCTL(SCSI_IOCTL_PROBE_HOST)
  COMPATIBLE_IOCTL(SCSI_IOCTL_GET_PCI)
  #endif
  #ifdef CONFIG_BLOCK
  /* SG stuff */
  COMPATIBLE_IOCTL(SG_IO)
  COMPATIBLE_IOCTL(SG_GET_REQUEST_TABLE)
  COMPATIBLE_IOCTL(SG_SET_TIMEOUT)
  COMPATIBLE_IOCTL(SG_GET_TIMEOUT)
  COMPATIBLE_IOCTL(SG_EMULATED_HOST)
  COMPATIBLE_IOCTL(SG_GET_TRANSFORM)
  COMPATIBLE_IOCTL(SG_SET_RESERVED_SIZE)
  COMPATIBLE_IOCTL(SG_GET_RESERVED_SIZE)
  COMPATIBLE_IOCTL(SG_GET_SCSI_ID)
  COMPATIBLE_IOCTL(SG_SET_FORCE_LOW_DMA)
  COMPATIBLE_IOCTL(SG_GET_LOW_DMA)
  COMPATIBLE_IOCTL(SG_SET_FORCE_PACK_ID)
  COMPATIBLE_IOCTL(SG_GET_PACK_ID)
  COMPATIBLE_IOCTL(SG_GET_NUM_WAITING)
  COMPATIBLE_IOCTL(SG_SET_DEBUG)
  COMPATIBLE_IOCTL(SG_GET_SG_TABLESIZE)
  COMPATIBLE_IOCTL(SG_GET_COMMAND_Q)
  COMPATIBLE_IOCTL(SG_SET_COMMAND_Q)
  COMPATIBLE_IOCTL(SG_GET_VERSION_NUM)
  COMPATIBLE_IOCTL(SG_NEXT_CMD_LEN)
  COMPATIBLE_IOCTL(SG_SCSI_RESET)
  COMPATIBLE_IOCTL(SG_GET_REQUEST_TABLE)
  COMPATIBLE_IOCTL(SG_SET_KEEP_ORPHAN)
  COMPATIBLE_IOCTL(SG_GET_KEEP_ORPHAN)
  #endif
  };
  
  /*
   * Convert common ioctl arguments based on their command number
   *
   * Please do not add any code in here. Instead, implement
   * a compat_ioctl operation in the place that handleѕ the
   * ioctl for the native case.
   */
  static long do_ioctl_trans(unsigned int cmd,
  		 unsigned long arg, struct file *file)
  {
  	return -ENOIOCTLCMD;
  }
  
  static int compat_ioctl_check_table(unsigned int xcmd)
  {
  #ifdef CONFIG_BLOCK
  	int i;
  	const int max = ARRAY_SIZE(ioctl_pointer) - 1;
  
  	BUILD_BUG_ON(max >= (1 << 16));
  
  	/* guess initial offset into table, assuming a
  	   normalized distribution */
  	i = ((xcmd >> 16) * max) >> 16;
  
  	/* do linear search up first, until greater or equal */
  	while (ioctl_pointer[i] < xcmd && i < max)
  		i++;
  
  	/* then do linear search down */
  	while (ioctl_pointer[i] > xcmd && i > 0)
  		i--;
  
  	return ioctl_pointer[i] == xcmd;
  #else
  	return 0;
  #endif
  }
  
  COMPAT_SYSCALL_DEFINE3(ioctl, unsigned int, fd, unsigned int, cmd,
  		       compat_ulong_t, arg32)
  {
  	unsigned long arg = arg32;
  	struct fd f = fdget(fd);
  	int error = -EBADF;
  	if (!f.file)
  		goto out;
  
  	/* RED-PEN how should LSM module know it's handling 32bit? */
  	error = security_file_ioctl(f.file, cmd, arg);
  	if (error)
  		goto out_fput;
  
  	switch (cmd) {
  	/* these are never seen by ->ioctl(), no argument or int argument */
  	case FIOCLEX:
  	case FIONCLEX:
  	case FIFREEZE:
  	case FITHAW:
  	case FICLONE:
  		goto do_ioctl;
  	/* these are never seen by ->ioctl(), pointer argument */
  	case FIONBIO:
  	case FIOASYNC:
  	case FIOQSIZE:
  	case FS_IOC_FIEMAP:
  	case FIGETBSZ:
  	case FICLONERANGE:
  	case FIDEDUPERANGE:
  		goto found_handler;
  	/*
  	 * The next group is the stuff handled inside file_ioctl().
  	 * For regular files these never reach ->ioctl(); for
  	 * devices, sockets, etc. they do and one (FIONREAD) is
  	 * even accepted in some cases.  In all those cases
  	 * argument has the same type, so we can handle these
  	 * here, shunting them towards do_vfs_ioctl().
  	 * ->compat_ioctl() will never see any of those.
  	 */
  	/* pointer argument, never actually handled by ->ioctl() */
  	case FIBMAP:
  		goto found_handler;
  	/* handled by some ->ioctl(); always a pointer to int */
  	case FIONREAD:
  		goto found_handler;
  	/* these get messy on amd64 due to alignment differences */
  #if defined(CONFIG_X86_64)
  	case FS_IOC_RESVSP_32:
  	case FS_IOC_RESVSP64_32:
  		error = compat_ioctl_preallocate(f.file, 0, compat_ptr(arg));
  		goto out_fput;
  	case FS_IOC_UNRESVSP_32:
  	case FS_IOC_UNRESVSP64_32:
  		error = compat_ioctl_preallocate(f.file, FALLOC_FL_PUNCH_HOLE,
  				compat_ptr(arg));
  		goto out_fput;
  	case FS_IOC_ZERO_RANGE_32:
  		error = compat_ioctl_preallocate(f.file, FALLOC_FL_ZERO_RANGE,
  				compat_ptr(arg));
  		goto out_fput;
  #else
  	case FS_IOC_RESVSP:
  	case FS_IOC_RESVSP64:
  	case FS_IOC_UNRESVSP:
  	case FS_IOC_UNRESVSP64:
  	case FS_IOC_ZERO_RANGE:
  		goto found_handler;
  #endif
  
  	default:
  		if (f.file->f_op->compat_ioctl) {
  			error = f.file->f_op->compat_ioctl(f.file, cmd, arg);
  			if (error != -ENOIOCTLCMD)
  				goto out_fput;
  		}
  
  		if (!f.file->f_op->unlocked_ioctl)
  			goto do_ioctl;
  		break;
  	}
  
  	if (compat_ioctl_check_table(XFORM(cmd)))
  		goto found_handler;
  
  	error = do_ioctl_trans(cmd, arg, f.file);
  	if (error == -ENOIOCTLCMD)
  		error = -ENOTTY;
  
  	goto out_fput;
  
   found_handler:
  	arg = (unsigned long)compat_ptr(arg);
   do_ioctl:
  	error = do_vfs_ioctl(f.file, fd, cmd, arg);
   out_fput:
  	fdput(f);
   out:
  	return error;
  }
  
  static int __init init_sys32_ioctl_cmp(const void *p, const void *q)
  {
  	unsigned int a, b;
  	a = *(unsigned int *)p;
  	b = *(unsigned int *)q;
  	if (a > b)
  		return 1;
  	if (a < b)
  		return -1;
  	return 0;
  }
  
  static int __init init_sys32_ioctl(void)
  {
  	sort(ioctl_pointer, ARRAY_SIZE(ioctl_pointer), sizeof(*ioctl_pointer),
  		init_sys32_ioctl_cmp, NULL);
  	return 0;
  }
  __initcall(init_sys32_ioctl);
  