diff options
Diffstat (limited to 'src/evdev.c')
-rw-r--r-- | src/evdev.c | 465 |
1 files changed, 465 insertions, 0 deletions
diff --git a/src/evdev.c b/src/evdev.c new file mode 100644 index 000000000..d8fd40489 --- /dev/null +++ b/src/evdev.c @@ -0,0 +1,465 @@ +/* + * Copyright (c) 2015 Etienne Gemsa <etienne.gemsa@lse.epita.fr> + * Copyright (c) 2015-2016 Dmitry V. Levin <ldv@strace.io> + * Copyright (c) 2015-2020 The strace developers. + * All rights reserved. + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +#include "defs.h" + +#include "xlat/evdev_abs.h" +#include "xlat/evdev_ev.h" + +#ifdef HAVE_LINUX_INPUT_H + +# include <linux/ioctl.h> +# include "types/evdev.h" + +# include "xlat/evdev_autorepeat.h" +# include "xlat/evdev_ff_status.h" +# include "xlat/evdev_ff_types.h" +# include "xlat/evdev_keycode.h" +# include "xlat/evdev_leds.h" +# include "xlat/evdev_misc.h" +# include "xlat/evdev_mtslots.h" +# include "xlat/evdev_prop.h" +# include "xlat/evdev_relative_axes.h" +# include "xlat/evdev_snd.h" +# include "xlat/evdev_switch.h" + +/** Added by Linux commit v2.6.38-rc1~247^2~1^2~2^2~5 */ +# ifndef INPUT_PROP_MAX +# define INPUT_PROP_MAX 0x1f +# endif +# ifndef SYN_MAX +# define SYN_MAX 0xf +# endif + +/* + * Has to be included after struct_* type definitions, since _IO* macros + * used in fallback definitions require them for sizeof(). + */ +# define XLAT_MACROS_ONLY +# include "xlat/evdev_ioctl_cmds.h" +# undef XLAT_MACROS_ONLY + +# ifndef EVIOCGPROP +# define EVIOCGPROP(len) _IOR('E', 0x09, len) +# endif +# ifndef EVIOCGMTSLOTS +# define EVIOCGMTSLOTS(len) _IOR('E', 0x0a, len) +# endif +# ifndef EVIOCGSW +# define EVIOCGSW(len) _IOR('E', 0x1b, len) +# endif + +static int +abs_ioctl(struct tcb *const tcp, const unsigned int code, + const kernel_ulong_t arg) +{ + static const size_t orig_sz = offsetofend(struct_input_absinfo, flat); + static const size_t res_sz = offsetofend(struct_input_absinfo, + resolution); + + struct_input_absinfo absinfo; + size_t sz = _IOC_SIZE(code); + size_t read_sz = MIN(sz, sizeof(absinfo)); + + if (sz < orig_sz) + return RVAL_DECODED; + + tprints(", "); + + if (umoven_or_printaddr(tcp, arg, read_sz, &absinfo)) + return RVAL_IOCTL_DECODED; + + tprint_struct_begin(); + PRINT_FIELD_U(absinfo, value); + tprint_struct_next(); + PRINT_FIELD_U(absinfo, minimum); + + if (!abbrev(tcp)) { + tprint_struct_next(); + PRINT_FIELD_U(absinfo, maximum); + tprint_struct_next(); + PRINT_FIELD_U(absinfo, fuzz); + tprint_struct_next(); + PRINT_FIELD_U(absinfo, flat); + if (sz > orig_sz) { + if (sz >= res_sz) { + tprint_struct_next(); + PRINT_FIELD_U(absinfo, resolution); + } + if (sz != res_sz) { + tprint_struct_next(); + tprint_more_data_follows(); + } + } + } else { + tprint_struct_next(); + tprint_more_data_follows(); + } + + tprint_struct_end(); + + return RVAL_IOCTL_DECODED; +} + +static int +keycode_ioctl(struct tcb *const tcp, const kernel_ulong_t arg) +{ + tprints(", "); + + unsigned int keycode[2]; + + if (!umove_or_printaddr(tcp, arg, &keycode)) { + tprintf("[%u, ", keycode[0]); + printxval(evdev_keycode, keycode[1], "KEY_???"); + tprints("]"); + } + + return RVAL_IOCTL_DECODED; +} + +static int +keycode_V2_ioctl(struct tcb *const tcp, const kernel_ulong_t arg) +{ + tprints(", "); + + struct_input_keymap_entry ike; + + if (umove_or_printaddr(tcp, arg, &ike)) + return RVAL_IOCTL_DECODED; + + tprint_struct_begin(); + PRINT_FIELD_U(ike, flags); + tprint_struct_next(); + PRINT_FIELD_U(ike, len); + + if (!abbrev(tcp)) { + tprint_struct_next(); + PRINT_FIELD_U(ike, index); + tprint_struct_next(); + PRINT_FIELD_XVAL(ike, keycode, evdev_keycode, "KEY_???"); + tprint_struct_next(); + PRINT_FIELD_X_ARRAY(ike, scancode); + } else { + tprint_struct_next(); + tprint_more_data_follows(); + } + + tprint_struct_end(); + + return RVAL_IOCTL_DECODED; +} + +static int +getid_ioctl(struct tcb *const tcp, const kernel_ulong_t arg) +{ + tprints(", "); + + struct input_id id; + + if (!umove_or_printaddr(tcp, arg, &id)) { + tprint_struct_begin(); + PRINT_FIELD_U(id, bustype); + tprint_struct_next(); + PRINT_FIELD_U(id, vendor); + tprint_struct_next(); + PRINT_FIELD_U(id, product); + tprint_struct_next(); + PRINT_FIELD_U(id, version); + tprint_struct_end(); + } + + return RVAL_IOCTL_DECODED; +} + +static int +decode_bitset(struct tcb *const tcp, const kernel_ulong_t arg, + const struct xlat *decode_nr, const unsigned int max_nr, + const char *const dflt) +{ + tprints(", "); + + unsigned int size; + unsigned int size_bits; + + if ((kernel_ulong_t) tcp->u_rval > max_nr / 8) + size_bits = max_nr; + else + size_bits = tcp->u_rval * 8; + + size = ROUNDUP(ROUNDUP_DIV(size_bits, 8), current_wordsize); + + if (syserror(tcp) || !size) { + printaddr(arg); + + return RVAL_IOCTL_DECODED; + } + + char decoded_arg[size]; + + if (umove_or_printaddr(tcp, arg, &decoded_arg)) + return RVAL_IOCTL_DECODED; + + if (xlat_verbose(xlat_verbosity) != XLAT_STYLE_RAW) { + tprints("["); + + int bit_displayed = 0; + int i = next_set_bit(decoded_arg, 0, size_bits); + if (i < 0) { + tprints(" 0 "); + } else { + printxval(decode_nr, i, dflt); + + while ((i = next_set_bit(decoded_arg, i + 1, + size_bits)) > 0) { + if (abbrev(tcp) && bit_displayed >= 3) { + tprint_struct_next(); + tprint_more_data_follows(); + break; + } + tprints(", "); + printxval(decode_nr, i, dflt); + bit_displayed++; + } + } + + tprints("]"); + } + + if (xlat_verbose(xlat_verbosity) == XLAT_STYLE_VERBOSE) + tprints(" /* "); + + if (xlat_verbose(xlat_verbosity) != XLAT_STYLE_ABBREV) { + print_local_array_ex(tcp, decoded_arg, size / current_wordsize, + current_wordsize, print_xlong_array_member, + NULL, 0, NULL, NULL); + } + + if (xlat_verbose(xlat_verbosity) == XLAT_STYLE_VERBOSE) + tprints(" */"); + + return RVAL_IOCTL_DECODED; +} + +static int +mtslots_ioctl(struct tcb *const tcp, const unsigned int code, + const kernel_ulong_t arg) +{ + tprints(", "); + + const size_t size = _IOC_SIZE(code) / sizeof(int); + if (!size) { + printaddr(arg); + return RVAL_IOCTL_DECODED; + } + + int buffer[size]; + + if (umove_or_printaddr(tcp, arg, &buffer)) + return RVAL_IOCTL_DECODED; + + tprint_struct_begin(); + tprints_field_name("code"); + printxval(evdev_mtslots, buffer[0], "ABS_MT_???"); + + tprint_struct_next(); + tprints_field_name("values"); + tprints("["); + + unsigned int i; + for (i = 1; i < ARRAY_SIZE(buffer); i++) + tprintf("%s%d", i > 1 ? ", " : "", buffer[i]); + + tprints("]"); + tprint_struct_end(); + + return RVAL_IOCTL_DECODED; +} + +static int +repeat_ioctl(struct tcb *const tcp, const kernel_ulong_t arg) +{ + tprints(", "); + printpair_int(tcp, arg, "%u"); + return RVAL_IOCTL_DECODED; +} + +static int +bit_ioctl(struct tcb *const tcp, const unsigned int ev_nr, + const kernel_ulong_t arg) +{ + switch (ev_nr) { + case 0: + return decode_bitset(tcp, arg, evdev_ev, + EV_MAX, "EV_???"); + case EV_KEY: + return decode_bitset(tcp, arg, evdev_keycode, + KEY_MAX, "KEY_???"); + case EV_REL: + return decode_bitset(tcp, arg, evdev_relative_axes, + REL_MAX, "REL_???"); + case EV_ABS: + return decode_bitset(tcp, arg, evdev_abs, + ABS_MAX, "ABS_???"); + case EV_MSC: + return decode_bitset(tcp, arg, evdev_misc, + MSC_MAX, "MSC_???"); + case EV_SW: + return decode_bitset(tcp, arg, evdev_switch, + SW_MAX, "SW_???"); + case EV_LED: + return decode_bitset(tcp, arg, evdev_leds, + LED_MAX, "LED_???"); + case EV_SND: + return decode_bitset(tcp, arg, evdev_snd, + SND_MAX, "SND_???"); + case EV_REP: + return decode_bitset(tcp, arg, evdev_autorepeat, + REP_MAX, "REP_???"); + case EV_FF: + return decode_bitset(tcp, arg, evdev_ff_types, + FF_MAX, "FF_???"); + case EV_PWR: + tprints(", "); + printnum_int(tcp, arg, "%d"); + return RVAL_IOCTL_DECODED; + case EV_FF_STATUS: + return decode_bitset(tcp, arg, evdev_ff_status, + FF_STATUS_MAX, "FF_STATUS_???"); + default: + tprints(", "); + printaddr(arg); + return RVAL_IOCTL_DECODED; + } +} + +static int +evdev_read_ioctl(struct tcb *const tcp, const unsigned int code, + const kernel_ulong_t arg) +{ + /* fixed-number fixed-length commands */ + switch (code) { + case EVIOCGVERSION: + tprints(", "); + printnum_int(tcp, arg, "%#x"); + return RVAL_IOCTL_DECODED; + case EVIOCGEFFECTS: + tprints(", "); + printnum_int(tcp, arg, "%u"); + return RVAL_IOCTL_DECODED; + case EVIOCGID: + return getid_ioctl(tcp, arg); + case EVIOCGREP: + return repeat_ioctl(tcp, arg); + case EVIOCGKEYCODE: + return keycode_ioctl(tcp, arg); + case EVIOCGKEYCODE_V2: + return keycode_V2_ioctl(tcp, arg); + } + + /* fixed-number variable-length commands */ + switch (_IOC_NR(code)) { + case _IOC_NR(EVIOCGMTSLOTS(0)): + return mtslots_ioctl(tcp, code, arg); + case _IOC_NR(EVIOCGNAME(0)): + case _IOC_NR(EVIOCGPHYS(0)): + case _IOC_NR(EVIOCGUNIQ(0)): + tprints(", "); + if (syserror(tcp)) + printaddr(arg); + else + printstrn(tcp, arg, tcp->u_rval); + return RVAL_IOCTL_DECODED; + case _IOC_NR(EVIOCGPROP(0)): + return decode_bitset(tcp, arg, evdev_prop, + INPUT_PROP_MAX, "PROP_???"); + case _IOC_NR(EVIOCGSND(0)): + return decode_bitset(tcp, arg, evdev_snd, + SND_MAX, "SND_???"); + case _IOC_NR(EVIOCGSW(0)): + return decode_bitset(tcp, arg, evdev_switch, + SW_MAX, "SW_???"); + case _IOC_NR(EVIOCGKEY(0)): + return decode_bitset(tcp, arg, evdev_keycode, + KEY_MAX, "KEY_???"); + case _IOC_NR(EVIOCGLED(0)): + return decode_bitset(tcp, arg, evdev_leds, + LED_MAX, "LED_???"); + } + + /* multi-number fixed-length commands */ + if ((_IOC_NR(code) & ~ABS_MAX) == _IOC_NR(EVIOCGABS(0))) + return abs_ioctl(tcp, code, arg); + + /* multi-number variable-length commands */ + if ((_IOC_NR(code) & ~EV_MAX) == _IOC_NR(EVIOCGBIT(0, 0))) + return bit_ioctl(tcp, _IOC_NR(code) & EV_MAX, arg); + + return 0; +} + +static int +evdev_write_ioctl(struct tcb *const tcp, const unsigned int code, + const kernel_ulong_t arg) +{ + /* fixed-number fixed-length commands */ + switch (code) { + case EVIOCSREP: + return repeat_ioctl(tcp, arg); + case EVIOCSKEYCODE: + return keycode_ioctl(tcp, arg); + case EVIOCSKEYCODE_V2: + return keycode_V2_ioctl(tcp, arg); + case EVIOCRMFF: + tprintf(", %d", (int) arg); + return RVAL_IOCTL_DECODED; + case EVIOCGRAB: + case EVIOCREVOKE: + tprintf(", %" PRI_klu, arg); + return RVAL_IOCTL_DECODED; + case EVIOCSCLOCKID: + tprints(", "); + printnum_int(tcp, arg, "%u"); + return RVAL_IOCTL_DECODED; + } + + int rc = evdev_write_ioctl_mpers(tcp, code, arg); + + if (rc != RVAL_DECODED) + return rc; + + /* multi-number fixed-length commands */ + if ((_IOC_NR(code) & ~ABS_MAX) == _IOC_NR(EVIOCSABS(0))) + return abs_ioctl(tcp, code, arg); + + return 0; +} + +void +print_evdev_ff_type(const kernel_ulong_t val) +{ + printxval(evdev_ff_types, val, "FF_???"); +} + +int +evdev_ioctl(struct tcb *const tcp, + const unsigned int code, const kernel_ulong_t arg) +{ + switch (_IOC_DIR(code)) { + case _IOC_READ: + if (entering(tcp)) + return 0; + return evdev_read_ioctl(tcp, code, arg); + case _IOC_WRITE: + return evdev_write_ioctl(tcp, code, arg) | RVAL_DECODED; + default: + return RVAL_DECODED; + } +} + +#endif /* HAVE_LINUX_INPUT_H */ |