hush: implement "readonly" builtin
authorDenys Vlasenko <vda.linux@googlemail.com>
Mon, 17 Jul 2017 19:10:50 +0000 (21:10 +0200)
committerDenys Vlasenko <vda.linux@googlemail.com>
Mon, 17 Jul 2017 19:10:50 +0000 (21:10 +0200)
function                                             old     new   delta
builtin_readonly                                       -      70     +70
helper_export_local                                  152     174     +22
bltins1                                              348     360     +12
expand_one_var                                      1620    1625      +5
builtin_export                                       168     173      +5
set_pwd_var                                           36      40      +4
set_local_var                                        410     414      +4
set_vars_and_save_old                                 85      88      +3
set_local_var_from_halves                             24      27      +3
run_pipe                                            1551    1554      +3
run_list                                            1046    1048      +2
builtin_type                                         116     114      -2
------------------------------------------------------------------------------
(add/remove: 1/0 grow/shrink: 10/1 up/down: 133/-2)           Total: 131 bytes

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
shell/hush.c

index c8356f4b8552745349bbe2742ff5994309374b4d..a68986329c9c08dfbe2d387a8d68f35d429377c0 100644 (file)
@@ -48,7 +48,7 @@
  *      tilde expansion
  *      aliases
  *      builtins mandated by standards we don't support:
- *          [un]alias, command, fc, getopts, readonly, times:
+ *          [un]alias, command, fc, getopts, times:
  *          command -v CMD: print "/path/to/CMD"
  *              prints "CMD" for builtins
  *              prints "alias ALIAS='EXPANSION'" for aliases
@@ -57,8 +57,7 @@
  *          command [-p] CMD: run CMD, even if a function CMD also exists
  *              (can use this to override standalone shell as well)
  *              -p: use default $PATH
- *          readonly VAR[=VAL]...: make VARs readonly
- *          readonly [-p]: list all such VARs (-p has no effect in bash)
+ *          command BLTIN: disables special-ness (e.g. errors do not abort)
  *          getopts: getopt() for shells
  *          times: print getrusage(SELF/CHILDREN).ru_utime/ru_stime
  *          fc -l[nr] [BEG] [END]: list range of commands in history
 //config:      help
 //config:        export -n unexports variables. It is a bash extension.
 //config:
+//config:config HUSH_READONLY
+//config:      bool "readonly builtin"
+//config:      default y
+//config:      help
+//config:        Enable support for read-only variables.
+//config:
 //config:config HUSH_KILL
 //config:      bool "kill builtin (supports kill %jobspec)"
 //config:      default y
@@ -964,6 +969,9 @@ static int builtin_exit(char **argv) FAST_FUNC;
 #if ENABLE_HUSH_EXPORT
 static int builtin_export(char **argv) FAST_FUNC;
 #endif
+#if ENABLE_HUSH_READONLY
+static int builtin_readonly(char **argv) FAST_FUNC;
+#endif
 #if ENABLE_HUSH_JOB
 static int builtin_fg_bg(char **argv) FAST_FUNC;
 static int builtin_jobs(char **argv) FAST_FUNC;
@@ -1082,6 +1090,9 @@ static const struct built_in_command bltins1[] = {
 #if ENABLE_HUSH_READ
        BLTIN("read"     , builtin_read    , "Input into variable"),
 #endif
+#if ENABLE_HUSH_READONLY
+       BLTIN("readonly" , builtin_readonly, "Make variables read-only"),
+#endif
 #if ENABLE_HUSH_FUNCTIONS
        BLTIN("return"   , builtin_return  , "Return from function"),
 #endif
@@ -2052,19 +2063,10 @@ static const char* FAST_FUNC get_local_var_value(const char *name)
  * -1: clear export flag and unsetenv the variable
  * flg_read_only is set only when we handle -R var=val
  */
-#if !BB_MMU && ENABLE_HUSH_LOCAL
-/* all params are used */
-#elif BB_MMU && ENABLE_HUSH_LOCAL
-#define set_local_var(str, flg_export, local_lvl, flg_read_only) \
-       set_local_var(str, flg_export, local_lvl)
-#elif BB_MMU && !ENABLE_HUSH_LOCAL
-#define set_local_var(str, flg_export, local_lvl, flg_read_only) \
-       set_local_var(str, flg_export)
-#elif !BB_MMU && !ENABLE_HUSH_LOCAL
-#define set_local_var(str, flg_export, local_lvl, flg_read_only) \
-       set_local_var(str, flg_export, flg_read_only)
-#endif
-static int set_local_var(char *str, int flg_export, int local_lvl, int flg_read_only)
+static int set_local_var(char *str,
+               int flg_export UNUSED_PARAM,
+               int local_lvl UNUSED_PARAM,
+               int flg_read_only UNUSED_PARAM)
 {
        struct variable **var_pp;
        struct variable *cur;
@@ -2088,9 +2090,7 @@ static int set_local_var(char *str, int flg_export, int local_lvl, int flg_read_
 
                /* We found an existing var with this name */
                if (cur->flg_read_only) {
-#if !BB_MMU
                        if (!flg_read_only)
-#endif
                                bb_error_msg("%s: readonly variable", str);
                        free(str);
                        return -1;
@@ -2158,10 +2158,12 @@ static int set_local_var(char *str, int flg_export, int local_lvl, int flg_read_
 
  set_str_and_exp:
        cur->varstr = str;
-#if !BB_MMU
-       cur->flg_read_only = flg_read_only;
-#endif
  exp:
+#if !BB_MMU || ENABLE_HUSH_READONLY
+       if (flg_read_only != 0) {
+               cur->flg_read_only = flg_read_only;
+       }
+#endif
        if (flg_export == 1)
                cur->flg_export = 1;
        if (name_len == 4 && cur->varstr[0] == 'P' && cur->varstr[1] == 'S')
@@ -9308,12 +9310,11 @@ static void print_escaped(const char *s)
 }
 #endif
 
-#if ENABLE_HUSH_EXPORT || ENABLE_HUSH_LOCAL
-# if !ENABLE_HUSH_LOCAL
-#define helper_export_local(argv, exp, lvl) \
-       helper_export_local(argv, exp)
-# endif
-static void helper_export_local(char **argv, int exp, int lvl)
+#if ENABLE_HUSH_EXPORT || ENABLE_HUSH_LOCAL || ENABLE_HUSH_READONLY
+static int helper_export_local(char **argv,
+               int exp UNUSED_PARAM,
+               int ro UNUSED_PARAM,
+               int lvl UNUSED_PARAM)
 {
        do {
                char *name = *argv;
@@ -9346,7 +9347,7 @@ static void helper_export_local(char **argv, int exp, int lvl)
                                }
                        }
 # if ENABLE_HUSH_LOCAL
-                       if (exp == 0 /* local? */
+                       if (exp == 0 && ro == 0 /* local? */
                         && var && var->func_nest_level == lvl
                        ) {
                                /* "local x=abc; ...; local x" - ignore second local decl */
@@ -9357,16 +9358,23 @@ static void helper_export_local(char **argv, int exp, int lvl)
                         * bash does not put it in environment,
                         * but remembers that it is exported,
                         * and does put it in env when it is set later.
-                        * We just set it to "" and export. */
+                        * We just set it to "" and export.
+                        */
                        /* Or, it's "local NAME" (without =VALUE).
-                        * bash sets the value to "". */
+                        * bash sets the value to "".
+                        */
+                       /* Or, it's "readonly NAME" (without =VALUE).
+                        * bash remembers NAME and disallows its creation
+                        * in the future.
+                        */
                        name = xasprintf("%s=", name);
                } else {
                        /* (Un)exporting/making local NAME=VALUE */
                        name = xstrdup(name);
                }
-               set_local_var(name, /*exp:*/ exp, /*lvl:*/ lvl, /*ro:*/ 0);
+               set_local_var(name, /*exp:*/ exp, /*lvl:*/ lvl, /*ro:*/ ro);
        } while (*++argv);
+       return EXIT_SUCCESS;
 }
 #endif
 
@@ -9412,9 +9420,7 @@ static int FAST_FUNC builtin_export(char **argv)
                return EXIT_SUCCESS;
        }
 
-       helper_export_local(argv, (opt_unexport ? -1 : 1), 0);
-
-       return EXIT_SUCCESS;
+       return helper_export_local(argv, /*exp:*/ (opt_unexport ? -1 : 1), /*ro:*/ 0, /*lvl:*/ 0);
 }
 #endif
 
@@ -9425,11 +9431,32 @@ static int FAST_FUNC builtin_local(char **argv)
                bb_error_msg("%s: not in a function", argv[0]);
                return EXIT_FAILURE; /* bash compat */
        }
-       helper_export_local(argv, 0, G.func_nest_level);
-       return EXIT_SUCCESS;
+       argv++;
+       return helper_export_local(argv, /*exp:*/ 0, /*ro:*/ 0, /*lvl:*/ G.func_nest_level);
 }
 #endif
 
+#if ENABLE_HUSH_READONLY
+static int FAST_FUNC builtin_readonly(char **argv)
+{
+       if (*++argv == NULL) {
+               /* bash: readonly [-p]: list all readonly VARs
+                * (-p has no effect in bash)
+                */
+               struct variable *e;
+               for (e = G.top_var; e; e = e->next) {
+                       if (e->flg_read_only) {
+//TODO: quote value: readonly VAR='VAL'
+                               printf("readonly %s\n", e->varstr);
+                       }
+               }
+               return EXIT_SUCCESS;
+       }
+       return helper_export_local(argv, /*exp:*/ 0, /*ro:*/ 1, /*lvl:*/ 0);
+}
+#endif
+
+
 #if ENABLE_HUSH_UNSET
 /* http://www.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#unset */
 static int FAST_FUNC builtin_unset(char **argv)