diff --git a/base/util.jl b/base/util.jl index df9e29790deb66..a587f854e48f66 100644 --- a/base/util.jl +++ b/base/util.jl @@ -257,7 +257,80 @@ 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, ": ") @@ -265,11 +338,11 @@ function getpass(input::TTY, output::IO, prompt::AbstractString) 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