From 8f60f7ab05f6dfa34da4b88b379a1ad43c5dd636 Mon Sep 17 00:00:00 2001 From: Keith Winstein Date: Sat, 14 Apr 2012 16:39:07 -0400 Subject: [PATCH] Convey locale-related environment variables as backup, and nicer error. Fixes #74. --- man/mosh-server.1 | 6 +++++ man/mosh.1 | 12 ++++++++++ scripts/mosh | 18 +++++++++++++++ src/examples/benchmark.cc | 2 +- src/examples/parse.cc | 2 +- src/examples/termemu.cc | 2 +- src/frontend/mosh-server.cc | 31 ++++++++++++++++++++++--- src/frontend/stmclient.cc | 7 +++++- src/util/locale_utils.cc | 45 +++++++++++++++++++++++++++++++------ src/util/locale_utils.h | 4 +++- 10 files changed, 114 insertions(+), 15 deletions(-) diff --git a/man/mosh-server.1 b/man/mosh-server.1 index 43cc906..16d65d4 100644 --- a/man/mosh-server.1 +++ b/man/mosh-server.1 @@ -76,6 +76,12 @@ UDP port number to bind .B \-c \fICOLORS\fP Number of colors to advertise to applications through TERM (e.g. 8, 256) +.TP +.B \-l \fINAME=VALUE\fP +Locale-related environment variable to try as part of a fallback +environment, if the startup environment does not specify a character +set of UTF-8. + .SH EXAMPLE .nf diff --git a/man/mosh.1 b/man/mosh.1 index beaaa9c..1522107 100644 --- a/man/mosh.1 +++ b/man/mosh.1 @@ -64,6 +64,18 @@ pass between client and server. By default, \fBmosh\fP uses the ports between 60000 and 61000, but allows the user to request a particular UDP port instead. +\fBmosh\fP will do its best to arrange a UTF-8 character set locale on +the client and server. The client must have locale-related environment +variables that specify UTF-8. \fBmosh\fP will pass these client +variables to the \fBmosh-server\fP on its command line, but in most +cases they will not need to be used. \fBmosh-server\fP first attempts +to use its own locale-related environment variables, which come from +the system default configuration (sometimes /etc/default/locale) or +from having been passed over the SSH connection. But if these +variables don't call for the use of UTF-8, \fBmosh-server\fP will +apply the locale-related environment variables from the client and try +again. + .SH OPTIONS .TP .B \fIcommand\fP diff --git a/scripts/mosh b/scripts/mosh index c11e0ec..24bbe69 100755 --- a/scripts/mosh +++ b/scripts/mosh @@ -196,6 +196,10 @@ if ( $pid == 0 ) { # child push @server, ( '-p', $port_request ); } + for ( &locale_vars ) { + push @server, ( '-l', $_ ); + } + if ( scalar @command > 0 ) { push @server, '--', @command; } @@ -237,3 +241,17 @@ if ( $pid == 0 ) { # child } sub shell_quote { join ' ', map {(my $a = $_) =~ s/'/'\\''/g; "'$a'"} @_ } + +sub locale_vars { + my @names = qw[LANG LANGUAGE LC_CTYPE LC_NUMERIC LC_TIME LC_COLLATE LC_MONETARY LC_MESSAGES LC_PAPER LC_NAME LC_ADDRESS LC_TELEPHONE LC_MEASUREMENT LC_IDENTIFICATION LC_ALL]; + + my @assignments; + + for ( @names ) { + if ( defined $ENV{ $_ } ) { + push @assignments, $_ . q{=} . $ENV{ $_ }; + } + } + + return @assignments; +} diff --git a/src/examples/benchmark.cc b/src/examples/benchmark.cc index cecea4e..26362f2 100644 --- a/src/examples/benchmark.cc +++ b/src/examples/benchmark.cc @@ -61,7 +61,7 @@ int main( void ) /* Adopt native locale */ set_native_locale(); - assert_utf8_locale(); + assert( is_utf8_locale() ); for ( int i = 0; i < ITERATIONS; i++ ) { /* type a character */ diff --git a/src/examples/parse.cc b/src/examples/parse.cc index 728ff6d..87eea42 100644 --- a/src/examples/parse.cc +++ b/src/examples/parse.cc @@ -57,7 +57,7 @@ int main( int argc __attribute__((unused)), struct termios saved_termios, raw_termios, child_termios; set_native_locale(); - assert_utf8_locale(); + assert( is_utf8_locale() ); if ( tcgetattr( STDIN_FILENO, &saved_termios ) < 0 ) { perror( "tcgetattr" ); diff --git a/src/examples/termemu.cc b/src/examples/termemu.cc index 9f7fd51..90b4372 100644 --- a/src/examples/termemu.cc +++ b/src/examples/termemu.cc @@ -62,7 +62,7 @@ int main( void ) struct termios saved_termios, raw_termios, child_termios; set_native_locale(); - assert_utf8_locale(); + assert( is_utf8_locale() ); if ( tcgetattr( STDIN_FILENO, &saved_termios ) < 0 ) { perror( "tcgetattr" ); diff --git a/src/frontend/mosh-server.cc b/src/frontend/mosh-server.cc index af24992..abd33d9 100644 --- a/src/frontend/mosh-server.cc +++ b/src/frontend/mosh-server.cc @@ -72,7 +72,7 @@ using namespace std; void print_usage( const char *argv0 ) { - fprintf( stderr, "Usage: %s new [-s] [-v] [-i LOCALADDR] [-p PORT] [-c COLORS] [-- COMMAND...]\n", argv0 ); + fprintf( stderr, "Usage: %s new [-s] [-v] [-i LOCALADDR] [-p PORT] [-c COLORS] [-l NAME=VALUE] [-- COMMAND...]\n", argv0 ); } /* Simple spinloop */ @@ -113,6 +113,7 @@ int main( int argc, char *argv[] ) int colors = 0; bool verbose = false; /* don't close stdin/stdout/stderr */ /* Will cause mosh-server not to correctly detach on old versions of sshd. */ + list locale_vars; /* strip off command */ for ( int i = 0; i < argc; i++ ) { @@ -130,7 +131,7 @@ int main( int argc, char *argv[] ) && (strcmp( argv[ 1 ], "new" ) == 0) ) { /* new option syntax */ int opt; - while ( (opt = getopt( argc - 1, argv + 1, "i:p:c:sv" )) != -1 ) { + while ( (opt = getopt( argc - 1, argv + 1, "i:p:c:svl:" )) != -1 ) { switch ( opt ) { case 'i': desired_ip = optarg; @@ -148,6 +149,9 @@ int main( int argc, char *argv[] ) case 'v': verbose = true; break; + case 'l': + locale_vars.push_back( string( optarg ) ); + break; default: print_usage( argv[ 0 ] ); /* don't die on unknown options */ @@ -203,7 +207,28 @@ int main( int argc, char *argv[] ) /* Adopt implementation locale */ set_native_locale(); - assert_utf8_locale(); + if ( !is_utf8_locale() ) { + /* apply locale-related environment variables from client */ + clear_locale_variables(); + for ( list::const_iterator i = locale_vars.begin(); + i != locale_vars.end(); + i++ ) { + char *env_string = strdup( i->c_str() ); + assert( env_string ); + if ( 0 != putenv( env_string ) ) { + perror( "putenv" ); + } + } + + /* check again */ + set_native_locale(); + if ( !is_utf8_locale() ) { + fprintf( stderr, "mosh-server needs a UTF-8 native locale to run.\n\n" ); + fprintf( stderr, "Unfortunately, the locale environment variables currently specify\nthe character set \"%s\".\n\n", locale_charset() ); + int unused __attribute((unused)) = system( "locale" ); + exit( 1 ); + } + } try { return run_server( desired_ip, desired_port, command, colors, verbose ); diff --git a/src/frontend/stmclient.cc b/src/frontend/stmclient.cc index 1d1a1a6..7df8cf6 100644 --- a/src/frontend/stmclient.cc +++ b/src/frontend/stmclient.cc @@ -49,7 +49,12 @@ void STMClient::init( void ) { - assert_utf8_locale(); + if ( !is_utf8_locale() ) { + fprintf( stderr, "mosh-client needs a UTF-8 native locale to run.\n\n" ); + fprintf( stderr, "Unfortunately, the locale environment variables currently specify\nthe character set \"%s\".\n\n", locale_charset() ); + int unused __attribute((unused)) = system( "locale" ); + exit( 1 ); + } /* Verify terminal configuration */ if ( tcgetattr( STDIN_FILENO, &saved_termios ) < 0 ) { diff --git a/src/util/locale_utils.cc b/src/util/locale_utils.cc index 307ad49..90b84c5 100644 --- a/src/util/locale_utils.cc +++ b/src/util/locale_utils.cc @@ -29,19 +29,50 @@ #include "locale_utils.h" -void assert_utf8_locale( void ) { - /* Verify locale calls for UTF-8 */ - if ( strcmp( nl_langinfo( CODESET ), "UTF-8" ) != 0 && - strcmp( nl_langinfo( CODESET ), "utf-8" ) != 0 ) { - fprintf( stderr, "mosh requires a UTF-8 locale.\n" ); - exit( 1 ); +const char *locale_charset( void ) +{ + static const char ASCII_name[] = "US-ASCII (ANSI_X3.4-1968)"; + + /* Produce more pleasant name of US-ASCII */ + const char *ret = nl_langinfo( CODESET ); + + if ( strcmp( ret, "ANSI_X3.4-1968" ) == 0 ) { + ret = ASCII_name; } + + return ret; +} + +bool is_utf8_locale( void ) { + /* Verify locale calls for UTF-8 */ + if ( strcmp( locale_charset(), "UTF-8" ) != 0 && + strcmp( locale_charset(), "utf-8" ) != 0 ) { + return 0; + } + return 1; } void set_native_locale( void ) { /* Adopt native locale */ if ( NULL == setlocale( LC_ALL, "" ) ) { perror( "setlocale" ); - exit( 1 ); } } + +void clear_locale_variables( void ) { + unsetenv( "LANG" ); + unsetenv( "LANGUAGE" ); + unsetenv( "LC_CTYPE" ); + unsetenv( "LC_NUMERIC" ); + unsetenv( "LC_TIME" ); + unsetenv( "LC_COLLATE" ); + unsetenv( "LC_MONETARY" ); + unsetenv( "LC_MESSAGES" ); + unsetenv( "LC_PAPER" ); + unsetenv( "LC_NAME" ); + unsetenv( "LC_ADDRESS" ); + unsetenv( "LC_TELEPHONE" ); + unsetenv( "LC_MEASUREMENT" ); + unsetenv( "LC_IDENTIFICATION" ); + unsetenv( "LC_ALL" ); +} diff --git a/src/util/locale_utils.h b/src/util/locale_utils.h index d8ea64c..029b901 100644 --- a/src/util/locale_utils.h +++ b/src/util/locale_utils.h @@ -19,7 +19,9 @@ #ifndef LOCALE_UTILS_HPP #define LOCALE_UTILS_HPP -void assert_utf8_locale( void ); +const char *locale_charset( void ); +bool is_utf8_locale( void ); void set_native_locale( void ); +void clear_locale_variables( void ); #endif