diff options
Diffstat (limited to 'drivers/tty/tty_ioctl.c')
| -rw-r--r-- | drivers/tty/tty_ioctl.c | 354 |
1 files changed, 178 insertions, 176 deletions
diff --git a/drivers/tty/tty_ioctl.c b/drivers/tty/tty_ioctl.c index ce511557b98b..90c70d8d14e3 100644 --- a/drivers/tty/tty_ioctl.c +++ b/drivers/tty/tty_ioctl.c @@ -7,6 +7,7 @@ * discipline handling modules (like SLIP). */ +#include <linux/bits.h> #include <linux/types.h> #include <linux/termios.h> #include <linux/errno.h> @@ -27,34 +28,23 @@ #include <asm/io.h> #include <linux/uaccess.h> -#undef TTY_DEBUG_WAIT_UNTIL_SENT - -#ifdef TTY_DEBUG_WAIT_UNTIL_SENT -# define tty_debug_wait_until_sent(tty, f, args...) tty_debug(tty, f, ##args) -#else -# define tty_debug_wait_until_sent(tty, f, args...) do {} while (0) -#endif - #undef DEBUG /* * Internal flag options for termios setting behavior */ -#define TERMIOS_FLUSH 1 -#define TERMIOS_WAIT 2 -#define TERMIOS_TERMIO 4 -#define TERMIOS_OLD 8 - +#define TERMIOS_FLUSH BIT(0) +#define TERMIOS_WAIT BIT(1) +#define TERMIOS_TERMIO BIT(2) +#define TERMIOS_OLD BIT(3) /** - * tty_chars_in_buffer - characters pending - * @tty: terminal + * tty_chars_in_buffer - characters pending + * @tty: terminal * - * Return the number of bytes of data in the device private - * output queue. If no private method is supplied there is assumed - * to be no queue on the device. + * Returns: the number of bytes of data in the device private output queue. If + * no private method is supplied there is assumed to be no queue on the device. */ - unsigned int tty_chars_in_buffer(struct tty_struct *tty) { if (tty->ops->chars_in_buffer) @@ -64,16 +54,15 @@ unsigned int tty_chars_in_buffer(struct tty_struct *tty) EXPORT_SYMBOL(tty_chars_in_buffer); /** - * tty_write_room - write queue space - * @tty: terminal + * tty_write_room - write queue space + * @tty: terminal * - * Return the number of bytes that can be queued to this device - * at the present time. The result should be treated as a guarantee - * and the driver cannot offer a value it later shrinks by more than - * the number of bytes written. If no method is provided 2K is always - * returned and data may be lost as there will be no flow control. + * Returns: the number of bytes that can be queued to this device at the present + * time. The result should be treated as a guarantee and the driver cannot + * offer a value it later shrinks by more than the number of bytes written. If + * no method is provided, 2K is always returned and data may be lost as there + * will be no flow control. */ - unsigned int tty_write_room(struct tty_struct *tty) { if (tty->ops->write_room) @@ -83,12 +72,12 @@ unsigned int tty_write_room(struct tty_struct *tty) EXPORT_SYMBOL(tty_write_room); /** - * tty_driver_flush_buffer - discard internal buffer - * @tty: terminal + * tty_driver_flush_buffer - discard internal buffer + * @tty: terminal * - * Discard the internal output buffer for this device. If no method - * is provided then either the buffer cannot be hardware flushed or - * there is no buffer driver side. + * Discard the internal output buffer for this device. If no method is provided, + * then either the buffer cannot be hardware flushed or there is no buffer + * driver side. */ void tty_driver_flush_buffer(struct tty_struct *tty) { @@ -98,107 +87,97 @@ void tty_driver_flush_buffer(struct tty_struct *tty) EXPORT_SYMBOL(tty_driver_flush_buffer); /** - * tty_unthrottle - flow control - * @tty: terminal + * tty_unthrottle - flow control + * @tty: terminal * - * Indicate that a tty may continue transmitting data down the stack. - * Takes the termios rwsem to protect against parallel throttle/unthrottle - * and also to ensure the driver can consistently reference its own - * termios data at this point when implementing software flow control. + * Indicate that a @tty may continue transmitting data down the stack. Takes + * the &tty_struct->termios_rwsem to protect against parallel + * throttle/unthrottle and also to ensure the driver can consistently reference + * its own termios data at this point when implementing software flow control. * - * Drivers should however remember that the stack can issue a throttle, - * then change flow control method, then unthrottle. + * Drivers should however remember that the stack can issue a throttle, then + * change flow control method, then unthrottle. */ - void tty_unthrottle(struct tty_struct *tty) { down_write(&tty->termios_rwsem); if (test_and_clear_bit(TTY_THROTTLED, &tty->flags) && tty->ops->unthrottle) tty->ops->unthrottle(tty); - tty->flow_change = 0; + tty->flow_change = TTY_FLOW_NO_CHANGE; up_write(&tty->termios_rwsem); } EXPORT_SYMBOL(tty_unthrottle); /** - * tty_throttle_safe - flow control - * @tty: terminal + * tty_throttle_safe - flow control + * @tty: terminal * - * Indicate that a tty should stop transmitting data down the stack. - * tty_throttle_safe will only attempt throttle if tty->flow_change is - * TTY_THROTTLE_SAFE. Prevents an accidental throttle due to race - * conditions when throttling is conditional on factors evaluated prior to - * throttling. + * Indicate that a @tty should stop transmitting data down the stack. + * tty_throttle_safe() will only attempt throttle if @tty->flow_change is + * %TTY_THROTTLE_SAFE. Prevents an accidental throttle due to race conditions + * when throttling is conditional on factors evaluated prior to throttling. * - * Returns 0 if tty is throttled (or was already throttled) + * Returns: %true if @tty is throttled (or was already throttled) */ - -int tty_throttle_safe(struct tty_struct *tty) +bool tty_throttle_safe(struct tty_struct *tty) { - int ret = 0; + guard(mutex)(&tty->throttle_mutex); - mutex_lock(&tty->throttle_mutex); - if (!tty_throttled(tty)) { - if (tty->flow_change != TTY_THROTTLE_SAFE) - ret = 1; - else { - set_bit(TTY_THROTTLED, &tty->flags); - if (tty->ops->throttle) - tty->ops->throttle(tty); - } - } - mutex_unlock(&tty->throttle_mutex); + if (tty_throttled(tty)) + return true; - return ret; + if (tty->flow_change != TTY_THROTTLE_SAFE) + return false; + + set_bit(TTY_THROTTLED, &tty->flags); + if (tty->ops->throttle) + tty->ops->throttle(tty); + + return true; } /** - * tty_unthrottle_safe - flow control - * @tty: terminal + * tty_unthrottle_safe - flow control + * @tty: terminal * - * Similar to tty_unthrottle() but will only attempt unthrottle - * if tty->flow_change is TTY_UNTHROTTLE_SAFE. Prevents an accidental - * unthrottle due to race conditions when unthrottling is conditional - * on factors evaluated prior to unthrottling. + * Similar to tty_unthrottle() but will only attempt unthrottle if + * @tty->flow_change is %TTY_UNTHROTTLE_SAFE. Prevents an accidental unthrottle + * due to race conditions when unthrottling is conditional on factors evaluated + * prior to unthrottling. * - * Returns 0 if tty is unthrottled (or was already unthrottled) + * Returns: %true if @tty is unthrottled (or was already unthrottled) */ - -int tty_unthrottle_safe(struct tty_struct *tty) +bool tty_unthrottle_safe(struct tty_struct *tty) { - int ret = 0; + guard(mutex)(&tty->throttle_mutex); - mutex_lock(&tty->throttle_mutex); - if (tty_throttled(tty)) { - if (tty->flow_change != TTY_UNTHROTTLE_SAFE) - ret = 1; - else { - clear_bit(TTY_THROTTLED, &tty->flags); - if (tty->ops->unthrottle) - tty->ops->unthrottle(tty); - } - } - mutex_unlock(&tty->throttle_mutex); + if (!tty_throttled(tty)) + return true; - return ret; + if (tty->flow_change != TTY_UNTHROTTLE_SAFE) + return false; + + clear_bit(TTY_THROTTLED, &tty->flags); + if (tty->ops->unthrottle) + tty->ops->unthrottle(tty); + + return true; } /** - * tty_wait_until_sent - wait for I/O to finish - * @tty: tty we are waiting for - * @timeout: how long we will wait + * tty_wait_until_sent - wait for I/O to finish + * @tty: tty we are waiting for + * @timeout: how long we will wait * - * Wait for characters pending in a tty driver to hit the wire, or - * for a timeout to occur (eg due to flow control) + * Wait for characters pending in a tty driver to hit the wire, or for a + * timeout to occur (eg due to flow control). * - * Locking: none + * Locking: none */ void tty_wait_until_sent(struct tty_struct *tty, long timeout) { - tty_debug_wait_until_sent(tty, "wait until sent, timeout=%ld\n", timeout); - if (!timeout) timeout = MAX_SCHEDULE_TIMEOUT; @@ -240,16 +219,15 @@ static void unset_locked_termios(struct tty_struct *tty, const struct ktermios * } /** - * tty_termios_copy_hw - copy hardware settings - * @new: New termios - * @old: Old termios + * tty_termios_copy_hw - copy hardware settings + * @new: new termios + * @old: old termios * - * Propagate the hardware specific terminal setting bits from - * the old termios structure to the new one. This is used in cases - * where the hardware does not support reconfiguration or as a helper - * in some cases where only minimal reconfiguration is supported + * Propagate the hardware specific terminal setting bits from the @old termios + * structure to the @new one. This is used in cases where the hardware does not + * support reconfiguration or as a helper in some cases where only minimal + * reconfiguration is supported. */ - void tty_termios_copy_hw(struct ktermios *new, const struct ktermios *old) { /* The bits a dumb device handles in software. Smart devices need @@ -262,30 +240,30 @@ void tty_termios_copy_hw(struct ktermios *new, const struct ktermios *old) EXPORT_SYMBOL(tty_termios_copy_hw); /** - * tty_termios_hw_change - check for setting change - * @a: termios - * @b: termios to compare + * tty_termios_hw_change - check for setting change + * @a: termios + * @b: termios to compare * - * Check if any of the bits that affect a dumb device have changed - * between the two termios structures, or a speed change is needed. + * Check if any of the bits that affect a dumb device have changed between the + * two termios structures, or a speed change is needed. + * + * Returns: %true if change is needed */ - -int tty_termios_hw_change(const struct ktermios *a, const struct ktermios *b) +bool tty_termios_hw_change(const struct ktermios *a, const struct ktermios *b) { if (a->c_ispeed != b->c_ispeed || a->c_ospeed != b->c_ospeed) - return 1; + return true; if ((a->c_cflag ^ b->c_cflag) & ~(HUPCL | CREAD | CLOCAL)) - return 1; - return 0; + return true; + return false; } EXPORT_SYMBOL(tty_termios_hw_change); /** - * tty_get_char_size - get size of a character - * @cflag: termios cflag value + * tty_get_char_size - get size of a character + * @cflag: termios cflag value * - * Get the size (in bits) of a character depending on @cflag's %CSIZE - * setting. + * Returns: size (in bits) of a character depending on @cflag's %CSIZE setting */ unsigned char tty_get_char_size(unsigned int cflag) { @@ -304,13 +282,14 @@ unsigned char tty_get_char_size(unsigned int cflag) EXPORT_SYMBOL_GPL(tty_get_char_size); /** - * tty_get_frame_size - get size of a frame - * @cflag: termios cflag value + * tty_get_frame_size - get size of a frame + * @cflag: termios cflag value * - * Get the size (in bits) of a frame depending on @cflag's %CSIZE, %CSTOPB, - * and %PARENB setting. The result is a sum of character size, start and - * stop bits -- one bit each -- second stop bit (if set), and parity bit - * (if set). + * Get the size (in bits) of a frame depending on @cflag's %CSIZE, %CSTOPB, and + * %PARENB setting. The result is a sum of character size, start and stop bits + * -- one bit each -- second stop bit (if set), and parity bit (if set). + * + * Returns: size (in bits) of a frame depending on @cflag's setting. */ unsigned char tty_get_frame_size(unsigned int cflag) { @@ -328,16 +307,15 @@ unsigned char tty_get_frame_size(unsigned int cflag) EXPORT_SYMBOL_GPL(tty_get_frame_size); /** - * tty_set_termios - update termios values - * @tty: tty to update - * @new_termios: desired new value + * tty_set_termios - update termios values + * @tty: tty to update + * @new_termios: desired new value * - * Perform updates to the termios values set on this terminal. - * A master pty's termios should never be set. + * Perform updates to the termios values set on this @tty. A master pty's + * termios should never be set. * - * Locking: termios_rwsem + * Locking: &tty_struct->termios_rwsem */ - int tty_set_termios(struct tty_struct *tty, struct ktermios *new_termios) { struct ktermios old_termios; @@ -450,18 +428,19 @@ __weak int kernel_termios_to_user_termios(struct termios __user *u, #endif /* TCGETS2 */ /** - * set_termios - set termios values for a tty - * @tty: terminal device - * @arg: user data - * @opt: option information + * set_termios - set termios values for a tty + * @tty: terminal device + * @arg: user data + * @opt: option information * - * Helper function to prepare termios data and run necessary other - * functions before using tty_set_termios to do the actual changes. + * Helper function to prepare termios data and run necessary other functions + * before using tty_set_termios() to do the actual changes. * - * Locking: - * Called functions take ldisc and termios_rwsem locks + * Locking: called functions take &tty_struct->ldisc_sem and + * &tty_struct->termios_rwsem locks + * + * Returns: 0 on success, an error otherwise */ - static int set_termios(struct tty_struct *tty, void __user *arg, int opt) { struct ktermios tmp_termios; @@ -500,21 +479,42 @@ static int set_termios(struct tty_struct *tty, void __user *arg, int opt) tmp_termios.c_ispeed = tty_termios_input_baud_rate(&tmp_termios); tmp_termios.c_ospeed = tty_termios_baud_rate(&tmp_termios); - ld = tty_ldisc_ref(tty); + if (opt & (TERMIOS_FLUSH|TERMIOS_WAIT)) { +retry_write_wait: + retval = wait_event_interruptible(tty->write_wait, !tty_chars_in_buffer(tty)); + if (retval < 0) + return retval; - if (ld != NULL) { - if ((opt & TERMIOS_FLUSH) && ld->ops->flush_buffer) - ld->ops->flush_buffer(tty); - tty_ldisc_deref(ld); - } + if (tty_write_lock(tty, false) < 0) + goto retry_write_wait; - if (opt & TERMIOS_WAIT) { - tty_wait_until_sent(tty, 0); - if (signal_pending(current)) - return -ERESTARTSYS; - } + /* Racing writer? */ + if (tty_chars_in_buffer(tty)) { + tty_write_unlock(tty); + goto retry_write_wait; + } + + ld = tty_ldisc_ref(tty); + if (ld != NULL) { + if ((opt & TERMIOS_FLUSH) && ld->ops->flush_buffer) + ld->ops->flush_buffer(tty); + tty_ldisc_deref(ld); + } + + if ((opt & TERMIOS_WAIT) && tty->ops->wait_until_sent) { + tty->ops->wait_until_sent(tty, 0); + if (signal_pending(current)) { + tty_write_unlock(tty); + return -ERESTARTSYS; + } + } - tty_set_termios(tty, &tmp_termios); + tty_set_termios(tty, &tmp_termios); + + tty_write_unlock(tty); + } else { + tty_set_termios(tty, &tmp_termios); + } /* FIXME: Arguably if tmp_termios == tty->termios AND the actual requested termios was not tmp_termios then we may @@ -612,16 +612,16 @@ static void set_sgflags(struct ktermios *termios, int flags) } /** - * set_sgttyb - set legacy terminal values - * @tty: tty structure - * @sgttyb: pointer to old style terminal structure + * set_sgttyb - set legacy terminal values + * @tty: tty structure + * @sgttyb: pointer to old style terminal structure * - * Updates a terminal from the legacy BSD style terminal information - * structure. + * Updates a terminal from the legacy BSD style terminal information structure. * - * Locking: termios_rwsem + * Locking: &tty_struct->termios_rwsem + * + * Returns: 0 on success, an error otherwise */ - static int set_sgttyb(struct tty_struct *tty, struct sgttyb __user *sgttyb) { int retval; @@ -723,19 +723,22 @@ static int set_ltchars(struct tty_struct *tty, struct ltchars __user *ltchars) #endif /** - * tty_change_softcar - carrier change ioctl helper - * @tty: tty to update - * @arg: enable/disable CLOCAL + * tty_change_softcar - carrier change ioctl helper + * @tty: tty to update + * @enable: enable/disable %CLOCAL + * + * Perform a change to the %CLOCAL state and call into the driver layer to make + * it visible. * - * Perform a change to the CLOCAL state and call into the driver - * layer to make it visible. All done with the termios rwsem + * Locking: &tty_struct->termios_rwsem. + * + * Returns: 0 on success, an error otherwise */ - -static int tty_change_softcar(struct tty_struct *tty, int arg) +static int tty_change_softcar(struct tty_struct *tty, bool enable) { int ret = 0; - int bit = arg ? CLOCAL : 0; struct ktermios old; + tcflag_t bit = enable ? CLOCAL : 0; down_write(&tty->termios_rwsem); old = tty->termios; @@ -750,16 +753,15 @@ static int tty_change_softcar(struct tty_struct *tty, int arg) } /** - * tty_mode_ioctl - mode related ioctls - * @tty: tty for the ioctl - * @cmd: command - * @arg: ioctl argument + * tty_mode_ioctl - mode related ioctls + * @tty: tty for the ioctl + * @cmd: command + * @arg: ioctl argument * - * Perform non line discipline specific mode control ioctls. This - * is designed to be called by line disciplines to ensure they provide - * consistent mode setting. + * Perform non-line discipline specific mode control ioctls. This is designed + * to be called by line disciplines to ensure they provide consistent mode + * setting. */ - int tty_mode_ioctl(struct tty_struct *tty, unsigned int cmd, unsigned long arg) { struct tty_struct *real_tty; @@ -838,7 +840,7 @@ int tty_mode_ioctl(struct tty_struct *tty, unsigned int cmd, unsigned long arg) ret = -EFAULT; return ret; case TIOCSLCKTRMIOS: - if (!capable(CAP_SYS_ADMIN)) + if (!checkpoint_restore_ns_capable(&init_user_ns)) return -EPERM; copy_termios_locked(real_tty, &kterm); if (user_termios_to_kernel_termios(&kterm, @@ -855,7 +857,7 @@ int tty_mode_ioctl(struct tty_struct *tty, unsigned int cmd, unsigned long arg) ret = -EFAULT; return ret; case TIOCSLCKTRMIOS: - if (!capable(CAP_SYS_ADMIN)) + if (!checkpoint_restore_ns_capable(&init_user_ns)) return -EPERM; copy_termios_locked(real_tty, &kterm); if (user_termios_to_kernel_termios_1(&kterm, |
