Skip to content

Commit

Permalink
Implement getch() on Linux for musl
Browse files Browse the repository at this point in the history
This is a workaround for a broken `getpass()` on `musl`, which doesn't
interact with our `with_fake_pty()` properly.
  • Loading branch information
staticfloat committed Jun 10, 2022
1 parent eb52446 commit ded7f3a
Showing 1 changed file with 75 additions and 2 deletions.
77 changes: 75 additions & 2 deletions base/util.jl
Original file line number Diff line number Diff line change
Expand Up @@ -257,19 +257,92 @@ graphical interface.
"""
function getpass end

using Base.BinaryPlatforms: libc, HostPlatform

# Windows and musl require getpass() workarounds based on `getch()`
if Sys.iswindows()
_getch() = UInt8(ccall(:_getch, Cint, ()))
elseif libc(HostPlatform()) == "musl"
struct termios_t
c_iflag::Cuint
c_oflag::Cuint
c_cflag::Cuint
c_lflag::Cuint
c_line::Cuchar
c_cc::NTuple{32,Cuchar}
__c_ispeed::Cuint
__c_ospeed::Cuint

function termios_t(d::Dict = Dict())
return new(
Cuint(get(d, :c_iflag, 0)),
Cuint(get(d, :c_oflag, 0)),
Cuint(get(d, :c_cflag, 0)),
Cuint(get(d, :c_lflag, 0)),
Cchar(get(d, :c_line, 0)),
ntuple(idx -> Cuchar(get(d, :c_cc, zeros(UInt, 32))[idx]), 32),
Cuint(get(d, :__c_ispeed, 0)),
Cuint(get(d, :__c_ospeed, 0)),
)
end
end

function Base.Dict(termios::termios_t)
return Dict{Symbol,Any}(
:c_iflag => termios.c_iflag,
:c_oflag => termios.c_oflag,
:c_cflag => termios.c_cflag,
:c_lflag => termios.c_lflag,
:c_line => termios.c_line,
:c_cc => collect(termios.c_cc),
:__c_ispeed => termios.__c_ispeed,
:__c_ospeed => termios.__c_ospeed,
)
end

_tcgetattr(fd, termios) = ccall(:tcgetattr, Cint, (Cint, Ref{termios_t}), fd, termios)
_tcsetattr(fd, flags, termios) = ccall(:tcsetattr, Cint, (Cint, Cint, Ref{termios_t}), fd, flags, termios)

# Taken from musl's `arch/x86_64/bits/termios.h`
const ICANON = Cuint(2)
const ECHO = Cuint(10)
const VTIME = 5
const VMIN = 6
const TCSANOW = 0
const TCSADRAIN = 1

function _getch()
old = termios_t()
systemerror("tcgetattr", _tcgetattr(0, old) < 0)

# Switch struct to dict for mutation
old = Dict(old)
old[:c_lflag] = old[:c_lflag] & ~ICANON
old[:c_lflag] = old[:c_lflag] & ~ECHO
old[:c_cc][VMIN] = 1
old[:c_cc][VTIME] = 0
systemerror("tcsetattr", _tcsetattr(0, TCSANOW, termios_t(old)) < 0)
val = read(stdin, 1)
old[:c_lflag] = old[:c_lflag] | ICANON
old[:c_lflag] = old[:c_lflag] | ECHO
systemerror("tcsetattr", _tcsetattr(0, TCSADRAIN, termios_t(old)) < 0)
return only(val)
end
end

if Sys.iswindows() || libc(HostPlatform()) == "musl"
function getpass(input::TTY, output::IO, prompt::AbstractString)
input === stdin || throw(ArgumentError("getpass only works for stdin"))
print(output, prompt, ": ")
flush(output)
s = SecretBuffer()
plen = 0
while true
c = UInt8(ccall(:_getch, Cint, ()))
c = _getch()
if c == 0xff || c == UInt8('\n') || c == UInt8('\r')
break # EOF or return
elseif c == 0x00 || c == 0xe0
ccall(:_getch, Cint, ()) # ignore function/arrow keys
_getch() # ignore function/arrow keys
elseif c == UInt8('\b') && plen > 0
plen -= 1 # delete last character on backspace
elseif !iscntrl(Char(c)) && plen < 128
Expand Down

0 comments on commit ded7f3a

Please sign in to comment.