hush: add readonly testcase, fix fallout

function                                             old     new   delta
helper_export_local                                  185     214     +29
run_pipe                                            1549    1560     +11
------------------------------------------------------------------------------
(add/remove: 0/0 grow/shrink: 2/0 up/down: 40/0)               Total: 40 bytes

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
diff --git a/shell/hush.c b/shell/hush.c
index 7771172..eab1284 100644
--- a/shell/hush.c
+++ b/shell/hush.c
@@ -7767,10 +7767,10 @@
 			if (new_env) {
 				argv = new_env;
 				while (*argv) {
-					set_local_var(*argv, /*flag:*/ 0);
-					/* Do we need to flag set_local_var() errors?
-					 * "assignment to readonly var" and "putenv error"
-					 */
+					if (set_local_var(*argv, /*flag:*/ 0)) {
+						/* assignment to readonly var / putenv error? */
+						rcode = 1;
+					}
 					argv++;
 				}
 			}
@@ -7795,10 +7795,10 @@
 					fprintf(stderr, " %s", p);
 				debug_printf_exec("set shell var:'%s'->'%s'\n",
 						*argv, p);
-				set_local_var(p, /*flag:*/ 0);
-				/* Do we need to flag set_local_var() errors?
-				 * "assignment to readonly var" and "putenv error"
-				 */
+				if (set_local_var(p, /*flag:*/ 0)) {
+					/* assignment to readonly var / putenv error? */
+					rcode = 1;
+				}
 				argv++;
 			}
 			if (G_x_mode)
@@ -9336,6 +9336,13 @@
 					continue;
 				}
 			}
+			if (flags & SETFLAG_MAKE_RO) {
+				/* readonly NAME (without =VALUE) */
+				if (var) {
+					var->flg_read_only = 1;
+					continue;
+				}
+			}
 # if ENABLE_HUSH_LOCAL
 			/* Is this "local" bltin? */
 			if (!(flags & (SETFLAG_EXPORT|SETFLAG_UNEXPORT|SETFLAG_MAKE_RO))) {
@@ -9364,7 +9371,8 @@
 			/* (Un)exporting/making local NAME=VALUE */
 			name = xstrdup(name);
 		}
-		set_local_var(name, flags);
+		if (set_local_var(name, flags))
+			return EXIT_FAILURE;
 	} while (*++argv);
 	return EXIT_SUCCESS;
 }
diff --git a/shell/hush_test/hush-vars/readonly0.right b/shell/hush_test/hush-vars/readonly0.right
new file mode 100644
index 0000000..9688d2e
--- /dev/null
+++ b/shell/hush_test/hush-vars/readonly0.right
@@ -0,0 +1,12 @@
+readonly a=A
+readonly b=B
+Ok:0
+hush: a=A: readonly variable
+Fail:1
+hush: a=A: readonly variable
+Fail:1
+hush: a=A: readonly variable
+Fail:1
+Visible:0
+hush: a: readonly variable
+Fail:1
diff --git a/shell/hush_test/hush-vars/readonly0.tests b/shell/hush_test/hush-vars/readonly0.tests
new file mode 100755
index 0000000..3845f76
--- /dev/null
+++ b/shell/hush_test/hush-vars/readonly0.tests
@@ -0,0 +1,24 @@
+readonly a=A
+b=B
+readonly b
+# readonly on already readonly var is harmless
+readonly b a
+readonly | grep '^readonly [ab]='
+# this should work
+export a b
+export -n a b
+echo Ok:$?
+env | grep -e^a= -e^b=  # shows nothing
+
+# these should all fail (despite the same value being assigned)
+# bash does not abort even in non-interactive more (in script)
+true
+a=A
+echo Fail:$?; true
+readonly a=A
+echo Fail:$?; true
+export a=A
+echo Fail:$?; true
+a=A echo Visible:$? # command still runs
+unset a
+echo Fail:$?; true