From e4b50334ec633d9ca7dbb970b48c72753a0a9d66 Mon Sep 17 00:00:00 2001 From: Jilles Tjoelker Date: Wed, 5 May 2010 21:48:40 +0000 Subject: [PATCH] sh: Apply locale vars on builtins, recognize LC_MESSAGES as a locale var. This allows doing things like LC_ALL=C some_builtin to run a builtin under a different locale, just like is possible with external programs. The immediate reason is that this allows making printf(1) a builtin without breaking things like LC_NUMERIC=C printf '%f\n' 1.2 This change also affects special builtins, as even though the assignment is persistent, the export is only to the builtin (unless the variable was already exported). Note: for this to work for builtins that also exist as external programs such as /bin/test, the setlocale() call must be under #ifndef SHELL. The shell will do the setlocale() calls which may not agree with the environment variables. --- bin/sh/eval.c | 4 + bin/sh/var.c | 75 ++++++++++-- bin/sh/var.h | 2 + tools/regression/bin/sh/builtins/locale1.0 | 133 +++++++++++++++++++++ 4 files changed, 207 insertions(+), 7 deletions(-) create mode 100644 tools/regression/bin/sh/builtins/locale1.0 diff --git a/bin/sh/eval.c b/bin/sh/eval.c index e93fb147b57a..c4495d04275a 100644 --- a/bin/sh/eval.c +++ b/bin/sh/eval.c @@ -937,6 +937,8 @@ evalcommand(union node *cmd, int flags, struct backcmd *backcmd) cmdentry.special = 1; if (cmdentry.special) listsetvar(cmdenviron); + if (argc > 0) + bltinsetlocale(); commandname = argv[0]; argptr = argv + 1; nextopt_optptr = NULL; /* initialize nextopt */ @@ -944,6 +946,8 @@ evalcommand(union node *cmd, int flags, struct backcmd *backcmd) exitstatus = (*builtinfunc[cmdentry.u.index])(argc, argv); flushall(); cmddone: + if (argc > 0) + bltinunsetlocale(); cmdenviron = NULL; out1 = &output; out2 = &errout; diff --git a/bin/sh/var.c b/bin/sh/var.c index d46e2c3e4649..75de239b7753 100644 --- a/bin/sh/var.c +++ b/bin/sh/var.c @@ -122,6 +122,14 @@ STATIC const struct varinit varinit[] = { STATIC struct var *vartab[VTABSIZE]; +STATIC const char *const locale_names[7] = { + "LC_COLLATE", "LC_CTYPE", "LC_MONETARY", + "LC_NUMERIC", "LC_TIME", "LC_MESSAGES", NULL +}; +STATIC const int locale_categories[7] = { + LC_COLLATE, LC_CTYPE, LC_MONETARY, LC_NUMERIC, LC_TIME, LC_MESSAGES, 0 +}; + STATIC struct var **hashvar(const char *); STATIC int varequal(const char *, const char *); STATIC int localevar(const char *); @@ -258,11 +266,7 @@ setvar(const char *name, const char *val, int flags) STATIC int localevar(const char *s) { - static const char *lnames[7] = { - "ALL", "COLLATE", "CTYPE", "MONETARY", - "NUMERIC", "TIME", NULL - }; - const char **ss; + const char *const *ss; if (*s != 'L') return 0; @@ -270,8 +274,10 @@ localevar(const char *s) return 1; if (strncmp(s + 1, "C_", 2) != 0) return 0; - for (ss = lnames; *ss ; ss++) - if (varequal(s + 3, *ss)) + if (varequal(s + 3, "ALL")) + return 1; + for (ss = locale_names; *ss ; ss++) + if (varequal(s + 3, *ss + 3)) return 1; return 0; } @@ -437,6 +443,61 @@ bltinlookup(const char *name, int doall) } +/* + * Set up locale for a builtin (LANG/LC_* assignments). + */ +void +bltinsetlocale(void) +{ + struct strlist *lp; + int act = 0; + char *loc, *locdef; + int i; + + for (lp = cmdenviron ; lp ; lp = lp->next) { + if (localevar(lp->text)) { + act = 1; + break; + } + } + if (!act) + return; + loc = bltinlookup("LC_ALL", 0); + INTOFF; + if (loc != NULL) { + setlocale(LC_ALL, loc); + INTON; + return; + } + locdef = bltinlookup("LANG", 0); + for (i = 0; locale_names[i] != NULL; i++) { + loc = bltinlookup(locale_names[i], 0); + if (loc == NULL) + loc = locdef; + if (loc != NULL) + setlocale(locale_categories[i], loc); + } + INTON; +} + +/* + * Undo the effect of bltinlocaleset(). + */ +void +bltinunsetlocale(void) +{ + struct strlist *lp; + + INTOFF; + for (lp = cmdenviron ; lp ; lp = lp->next) { + if (localevar(lp->text)) { + setlocale(LC_ALL, ""); + return; + } + } + INTON; +} + /* * Generate a list of exported variables. This routine is used to construct diff --git a/bin/sh/var.h b/bin/sh/var.h index 9c792c846ca7..6a02630842d3 100644 --- a/bin/sh/var.h +++ b/bin/sh/var.h @@ -107,6 +107,8 @@ struct strlist; void listsetvar(struct strlist *); char *lookupvar(const char *); char *bltinlookup(const char *, int); +void bltinsetlocale(void); +void bltinunsetlocale(void); char **environment(void); int showvarscmd(int, char **); int exportcmd(int, char **); diff --git a/tools/regression/bin/sh/builtins/locale1.0 b/tools/regression/bin/sh/builtins/locale1.0 new file mode 100644 index 000000000000..93a37dfc876c --- /dev/null +++ b/tools/regression/bin/sh/builtins/locale1.0 @@ -0,0 +1,133 @@ +# $FreeBSD$ +# Note: this test depends on strerror() using locale. + +failures=0 + +check() { + if ! eval "[ $1 ]"; then + echo "Failed: $1 at $2" + : $((failures += 1)) + fi +} + +unset LANG LC_ALL LC_COLLATE LC_CTYPE LC_MONETARY LC_NUMERIC LC_TIME LC_MESSAGES + +msgeng="No such file or directory" +msgdut="Bestand of map niet gevonden" + +# Verify C locale error message. +case $(command . /var/empty/foo 2>&1) in + *"$msgeng"*) ok=1 ;; + *) ok=0 ;; +esac +check '$ok -eq 1' $LINENO + +# Various locale variables that should not affect the message. +case $(LC_ALL=C command . /var/empty/foo 2>&1) in + *"$msgeng"*) ok=1 ;; + *) ok=0 ;; +esac +check '$ok -eq 1' $LINENO + +case $(LC_ALL=C LANG=nl_NL.ISO8859-1 command . /var/empty/foo 2>&1) in + *"$msgeng"*) ok=1 ;; + *) ok=0 ;; +esac +check '$ok -eq 1' $LINENO + +case $(LC_ALL=C LC_MESSAGES=nl_NL.ISO8859-1 command . /var/empty/foo 2>&1) in + *"$msgeng"*) ok=1 ;; + *) ok=0 ;; +esac +check '$ok -eq 1' $LINENO + +case $(LC_CTYPE=nl_NL.ISO8859-1 command . /var/empty/foo 2>&1) in + *"$msgeng"*) ok=1 ;; + *) ok=0 ;; +esac +check '$ok -eq 1' $LINENO + +# Verify Dutch message. +case $(export LANG=nl_NL.ISO8859-1; command . /var/empty/foo 2>&1) in + *"$msgdut"*) ok=1 ;; + *) ok=0 ;; +esac +check '$ok -eq 1' $LINENO + +case $(export LC_MESSAGES=nl_NL.ISO8859-1; command . /var/empty/foo 2>&1) in + *"$msgdut"*) ok=1 ;; + *) ok=0 ;; +esac +check '$ok -eq 1' $LINENO + +case $(export LC_ALL=nl_NL.ISO8859-1; command . /var/empty/foo 2>&1) in + *"$msgdut"*) ok=1 ;; + *) ok=0 ;; +esac +check '$ok -eq 1' $LINENO + +case $(LANG=nl_NL.ISO8859-1 command . /var/empty/foo 2>&1) in + *"$msgdut"*) ok=1 ;; + *) ok=0 ;; +esac +check '$ok -eq 1' $LINENO + +case $(LC_MESSAGES=nl_NL.ISO8859-1 command . /var/empty/foo 2>&1) in + *"$msgdut"*) ok=1 ;; + *) ok=0 ;; +esac +check '$ok -eq 1' $LINENO + +case $(LC_ALL=nl_NL.ISO8859-1 command . /var/empty/foo 2>&1) in + *"$msgdut"*) ok=1 ;; + *) ok=0 ;; +esac +check '$ok -eq 1' $LINENO + +# Verify that command assignments do not set the locale persistently. +case $(command . /var/empty/foo 2>&1) in + *"$msgeng"*) ok=1 ;; + *) ok=0 ;; +esac +check '$ok -eq 1' $LINENO + +case $(LANG=nl_NL.ISO8859-1 command . /var/empty/foo 2>&1; command . /var/empty/foo 2>&1) in + *"$msgdut"*"$msgeng"*) ok=1 ;; + *) ok=0 ;; +esac +check '$ok -eq 1' $LINENO + +case $(LC_MESSAGES=nl_NL.ISO8859-1 command . /var/empty/foo 2>&1; command . /var/empty/foo 2>&1) in + *"$msgdut"*"$msgeng"*) ok=1 ;; + *) ok=0 ;; +esac +check '$ok -eq 1' $LINENO + +case $(LC_ALL=nl_NL.ISO8859-1 command . /var/empty/foo 2>&1; command . /var/empty/foo 2>&1) in + *"$msgdut"*"$msgeng"*) ok=1 ;; + *) ok=0 ;; +esac +check '$ok -eq 1' $LINENO + +# Check special builtin; add colon invocation to avoid depending on certain fix. +case $(LC_ALL=nl_NL.ISO8859-1 . /var/empty/foo 2>&1; :) in + *"$msgdut"*) ok=1 ;; + *) ok=0 ;; +esac +check '$ok -eq 1' $LINENO + +# Assignments on special builtins are exported to that builtin; the export +# is not persistent. +case $(LC_ALL=nl_NL.ISO8859-1 . /dev/null; . /var/empty/foo 2>&1) in + *"$msgeng"*) ok=1 ;; + *) ok=0 ;; +esac +check '$ok -eq 1' $LINENO + +case $(export LC_ALL; LC_ALL=nl_NL.ISO8859-1 . /dev/null; . /var/empty/foo 2>&1) in + *"$msgdut"*) ok=1 ;; + *) ok=0 ;; +esac +check '$ok -eq 1' $LINENO + +exit $((failures > 0))