]> git.ipfire.org Git - thirdparty/bash.git/commitdiff
fix issue with redirections to bash input file descriptor; new minimal chmod builtin...
authorChet Ramey <chet.ramey@case.edu>
Wed, 12 Feb 2025 16:18:16 +0000 (11:18 -0500)
committerChet Ramey <chet.ramey@case.edu>
Wed, 12 Feb 2025 16:18:16 +0000 (11:18 -0500)
13 files changed:
CWRU/CWRU.chlog
MANIFEST
braces.c
builtins/kill.def
command.h
doc/bash.1
doc/bashref.texi
doc/version.texi
examples/loadables/Makefile.in
examples/loadables/chmod.c [new file with mode: 0644]
execute_cmd.c
input.c
redir.c

index 632e93d7a5374e7ff53cfb4e9eb54ea73b246516..d7386faf29581d239583b2377323f859c08dd995 100644 (file)
@@ -10929,3 +10929,59 @@ jobs.c
          return 257 just as if the child was not found, optionally printing
          an error message if JWAIT_PERROR is supplied
          From a discussion with Ian <saturns_rings@protonmail.com>
+
+                                   2/8
+                                   ---
+redir.c
+       - do_redirection_internal: keep track of whether the redirector fd
+         was active with a new variable. Not used yet, should be optimized
+         out.
+       - add_undo_fd_redirect: new undo function using r_move_input so we
+         don't need a dup redir and a separate close redir to undo. Not
+         used yet
+       - do_redirection_internal: only call check_bash_input if we're
+         creating an undo list and not running one
+
+command.h
+       - OUTPUT_REDIRECT: add r_output_force
+
+input.c
+       - check_bash_input: if we dup the bash input fd to another in
+         save_bash_input, return that fd. Continue to return -1 on error
+         and 0 if we don't do anything but sync
+
+execute_cmd.c
+       - execute_null_command: force a subshell if any of the redirections
+         involve the bash input buffered fd.
+         From a report by Martin D Kealey <martin@kurahaupo.gen.nz>
+
+                                  2/10
+                                  ----
+
+examples/loadables/chmod.c
+       - chmod: new loadable builtin, inspired by zhixu.liu@gmail.com in
+         https://savannah.gnu.org/patch/?10499
+
+                                  2/11
+                                  ----
+builtins/kill.def
+       - kill_builtin: in posix mode, return a failure status if any of the
+         pid/job arguments are not found or sending the signal fails. This
+         is consistent with
+         https://pubs.opengroup.org/onlinepubs/9799919799/utilities/kill.html#tag_20_64_14
+
+                                  2/12
+                                  ----
+braces.c
+       - brace_gobbler: take a new argument in which to return the type of
+         brace expansion (comma or sequence) we think we've found; change
+         all callers
+       - brace_expand: even if we find a BRACE_ARG_SEPARATOR, don't try to
+         perform expansion if brace_gobbler doesn't think we have a
+         BRACE_COMMA expansion
+       - valid_seqterm: new function, performs minimal validation on a
+         sequence expansion, used to skip over invalid sequence expressions
+         in brace_expand
+       - brace_expand: call valid_seqterm if brace_gobbler finds a BRACE_SEQ
+         expansion
+         Inspired by report from Robert Elz <kre@munnari.oz.au>
index 76917764f51450a865f775cbee11548bd1bec3fe..4a1af2962d5c07e6e61fe94548e450bab3eba7e4 100644 (file)
--- a/MANIFEST
+++ b/MANIFEST
@@ -786,6 +786,7 @@ examples/loadables/fdflags.c        f
 examples/loadables/finfo.c     f
 examples/loadables/fltexpr.c   f
 examples/loadables/cat.c       f
+examples/loadables/chmod.c     f
 examples/loadables/csv.c       f
 examples/loadables/dsv.c       f
 examples/loadables/kv.c                f
index 57c0b109df9c70f45c3a70020c8067bbee979bd1..2221e38d19b187ef50ca35fd0f4f6235bc368417 100644 (file)
--- a/braces.c
+++ b/braces.c
@@ -61,6 +61,11 @@ extern int errno;
 
 #define BRACE_SEQ_SPECIFIER    ".."
 
+/* What kind of brace expansion do we think we have? brace_gobbler() decides. */
+#define BRACE_COMMA    0x01
+#define BRACE_SEQ      0x02
+#define BRACE_NONE     0x04
+
 extern int asprintf (char **, const char *, ...) __attribute__((__format__ (printf, 2, 3)));
 
 /* Basic idea:
@@ -75,9 +80,10 @@ extern int asprintf (char **, const char *, ...) __attribute__((__format__ (prin
 /* The character which is used to separate arguments. */
 static const int brace_arg_separator = ',';
 
-static int brace_gobbler (char *, size_t, int *, int);
+static int brace_gobbler (char *, size_t, int *, int *, int);
 static char **expand_amble (char *, size_t, int);
 static char **expand_seqterm (char *, size_t);
+static int valid_seqterm (char *, size_t);
 static char **mkseq (intmax_t, intmax_t, intmax_t, int, size_t);
 static char **array_concat (char **, char **);
 
@@ -97,12 +103,12 @@ dump_result (a)
 char **
 brace_expand (char *text)
 {
-  register int start;
+  int start;
   size_t tlen;
   char *preamble, *postamble, *amble;
   size_t alen;
   char **tack, **result;
-  int i, j, c, c1;
+  int i, j, c, c1, etype;
 
   DECLARE_MBSTATE;
 
@@ -110,13 +116,13 @@ brace_expand (char *text)
   tlen = strlen (text);
   i = 0;
 #if defined (CSH_BRACE_COMPAT)
-  c = brace_gobbler (text, tlen, &i, '{');     /* } */
+  c = brace_gobbler (text, tlen, &i, (int *)NULL, '{');        /* } */
 #else
   /* Make sure that when we exit this loop, c == 0 or text[i] begins a
      valid brace expansion sequence. */
   do
     {
-      c = brace_gobbler (text, tlen, &i, '{'); /* } */
+      c = brace_gobbler (text, tlen, &i, (int *)NULL, '{');    /* } */
       if (i >= tlen)
        break;
       c1 = c;
@@ -125,7 +131,16 @@ brace_expand (char *text)
       if (c)
        {
          start = j = i + 1;    /* { */
-         c = brace_gobbler (text, tlen, &j, '}');
+         c = brace_gobbler (text, tlen, &j, &etype, '}');
+#if 1
+         /* One alternative is to perform validity checking on the sequence
+            terms here. If the sequence expression is invalid, we just skip
+            over the open brace and go on, leaving other brace expressions in
+            the candidate sequence expression to be expanded. */
+         if (etype == BRACE_SEQ && valid_seqterm (text + start, j - 1) == 0)
+           c = 0;
+#endif
+
          if (c == 0)           /* it's not */
            {
              i++;
@@ -160,7 +175,7 @@ brace_expand (char *text)
 
   /* Find the amble.  This is the stuff inside this set of braces. */
   start = ++i;
-  c = brace_gobbler (text, tlen, &i, '}');
+  c = brace_gobbler (text, tlen, &i, &etype, '}');
 
   /* What if there isn't a matching close brace? */
   if (c == 0)
@@ -206,8 +221,8 @@ brace_expand (char *text)
 #if defined (SHELL)
   INITIALIZE_MBSTATE;
 
-  /* If the amble does not contain an unquoted BRACE_ARG_SEPARATOR, then
-     just return without doing any expansion.  */
+  /* If the amble does not contain an unquoted BRACE_ARG_SEPARATOR, and we
+     think we have a BRACE_COMMA-separated sequence, then do no expansion. */
   j = 0;
   while (amble[j])
     {
@@ -218,14 +233,19 @@ brace_expand (char *text)
          continue;
        }
 
-      if (amble[j] == brace_arg_separator)
+      if (amble[j] == brace_arg_separator && etype == BRACE_COMMA)
        break;
 
       ADVANCE_CHAR (amble, alen, j);
     }
 
-  if (amble[j] == 0)
+  /* If we think we have a sequence expression, try to expand it. */
+  if (amble[j] == 0 && etype == BRACE_SEQ)
     {
+      /* The other alternative (see call to valid_seqterm() above) is to
+        perform the validity checking in expand_seqterm(). If we do this,
+        and the sequence isn't valid, we just treat the entire candidate
+        sequence expansion as a single unexpanded string. */
       tack = expand_seqterm (amble, alen);
       if (tack)
        goto add_tack;
@@ -293,7 +313,7 @@ expand_amble (char *text, size_t tlen, int flags)
   c = 1;
   while (c)
     {
-      c = brace_gobbler (text, tlen, &i, brace_arg_separator);
+      c = brace_gobbler (text, tlen, &i, (int *)NULL, brace_arg_separator);
 #if defined (SHELL)
       tem = substring (text, start, i);
 #else
@@ -455,6 +475,44 @@ mkseq (intmax_t start, intmax_t end, intmax_t incr, int type, size_t width)
   return (result);
 }
 
+/* For now. */
+static int
+valid_seqterm (char *text, size_t tlen)
+{
+  char *t, *lhs, *rhs;
+  int lhs_t, rhs_t;
+  int c;
+
+  c = text[tlen];
+  text[tlen] = '\0';   /* don't be tricked by something later in the string */
+  t = strstr (text, BRACE_SEQ_SPECIFIER);
+  text[tlen] = c;
+    
+  if (t == 0)
+    return 0;          /* invalid */
+  
+  lhs = text;
+  rhs = t + sizeof(BRACE_SEQ_SPECIFIER) - 1;
+
+  /*{*/
+  if (lhs[0] == BRACE_SEQ_SPECIFIER[0] || rhs[0] == '}')
+    return 0;          /* invalid */
+
+  /* Now figure out whether LHS and RHS are integers or letters.  Both
+     sides have to match. Minimal checking here, just enough to throw out the
+     obvious invalid candidates. */
+  lhs_t = (ISDIGIT (lhs[0]) || ((lhs[0] == '+' || lhs[0] == '-') && ISDIGIT (lhs[1]))) ? ST_INT :
+           (ISALPHA (lhs[0]) && lhs[1] == '.') ?  ST_CHAR : ST_BAD;
+
+  rhs_t = (ISDIGIT (rhs[0]) || ((rhs[0] == '+' || rhs[0] == '-') && ISDIGIT (rhs[1]))) ? ST_INT :
+/*{*/      (ISALPHA (rhs[0]) && (rhs[1] == '}' || rhs[1] == '.')) ? ST_CHAR : ST_BAD;
+
+  if (lhs_t != rhs_t || lhs_t == ST_BAD || rhs_t == ST_BAD)
+    return 0;          /* invalid */
+
+  return 1;
+}
+
 static char **
 expand_seqterm (char *text, size_t tlen)
 {
@@ -577,9 +635,9 @@ expand_seqterm (char *text, size_t tlen)
           an inner set of braces.       
 */
 static int
-brace_gobbler (char *text, size_t tlen, int *indx, int satisfy)
+brace_gobbler (char *text, size_t tlen, int *indx, int *typep, int satisfy)
 {
-  register int i, c, quoted, level, commas, pass_next;
+  int i, c, quoted, level, commas, pass_next, btype;
 #if defined (SHELL)
   size_t si;
   char *t;
@@ -587,6 +645,7 @@ brace_gobbler (char *text, size_t tlen, int *indx, int satisfy)
   DECLARE_MBSTATE;
 
   level = quoted = pass_next = 0;
+  btype = BRACE_NONE;
 #if defined (CSH_BRACE_COMPAT)
   commas = 1;
 #else
@@ -719,10 +778,16 @@ comsub:
        level--;
 #if !defined (CSH_BRACE_COMPAT)
       else if (satisfy == '}' && c == brace_arg_separator && level == 0)
-       commas++;
+       {
+         btype = BRACE_COMMA;
+         commas++;
+       }
       else if (satisfy == '}' && STREQN (text+i, BRACE_SEQ_SPECIFIER, 2) &&
                text[i+2] != satisfy && level == 0)
-       commas++;
+       {
+         btype = BRACE_SEQ;
+         commas++;
+       }
 #endif
 
 #if defined (SHELL)
@@ -733,6 +798,8 @@ comsub:
     }
 
   *indx = i;
+  if (typep)
+    *typep = btype;
   return (c);
 }
 
index f09c932affcbc6e51c7ccbffa0d5aeb934b74632..7b16cf499b5cf560d946d12eae91d137927f1d86 100644 (file)
@@ -84,7 +84,7 @@ static void kill_error (pid_t, int);
 int
 kill_builtin (WORD_LIST *list)
 {
-  int sig, any_succeeded, listing, saw_signal, dflags;
+  int sig, any_succeeded, any_failed, listing, saw_signal, dflags;
   char *sigspec, *word;
   pid_t pid;
   intmax_t pid_value;
@@ -96,7 +96,7 @@ kill_builtin (WORD_LIST *list)
     }
   CHECK_HELPOPT (list);
 
-  any_succeeded = listing = saw_signal = 0;
+  any_succeeded = any_failed = listing = saw_signal = 0;
   sig = SIGTERM;
   sigspec = "TERM";
 
@@ -199,6 +199,7 @@ use_sigspec:
                sh_invalidsig (sigspec);
              else
                kill_error (pid, errno);
+             any_failed++;
              CONTINUE_OR_FAIL;
            }
          else
@@ -247,6 +248,7 @@ use_sigspec:
                sh_invalidsig (sigspec);
              else
                kill_error (pid, errno);
+             any_failed++;
              CONTINUE_OR_FAIL;
            }
          else
@@ -262,7 +264,10 @@ use_sigspec:
       list = list->next;
     }
 
-  return (any_succeeded ? EXECUTION_SUCCESS : EXECUTION_FAILURE);
+  if (posixly_correct)
+    return ((any_failed == 0) ? EXECUTION_SUCCESS : EXECUTION_FAILURE);
+  else
+    return (any_succeeded ? EXECUTION_SUCCESS : EXECUTION_FAILURE);
 }
 
 static void
index 1c0681482268d333be83ea149e71e038cceb78d5..189b00def13c36c5ecedeffdf2c35babc7b19967 100644 (file)
--- a/command.h
+++ b/command.h
@@ -49,7 +49,7 @@ enum r_instruction {
   (ri == r_output_direction || ri == r_err_and_out)
 
 #define OUTPUT_REDIRECT(ri) \
-  (ri == r_output_direction || ri == r_input_output || ri == r_err_and_out || ri == r_append_err_and_out)
+  (ri == r_output_direction || ri == r_input_output || ri == r_err_and_out || ri == r_append_err_and_out || ri == r_output_force)
 
 #define INPUT_REDIRECT(ri) \
   (ri == r_input_direction || ri == r_inputa_direction || ri == r_input_output)
index fd86b83583184d5d307bacef50dc58daea96afb0..369dee85f651e9d0f3f71f9c070ee28657550dec 100644 (file)
@@ -1381,7 +1381,7 @@ below).
 .PP
 A
 .I variable
-may be assigned to by a statement of the form
+is assigned to using a statement of the form
 .RS
 .PP
 \fIname\fP=[\fIvalue\fP]
@@ -11348,7 +11348,7 @@ command that fails is part of the command list immediately following a
 .B while
 or
 .B until
-keyword,
+reserved word,
 part of the test following the
 .B if
 or
@@ -12547,7 +12547,7 @@ command is part of the command list immediately following a
 .B while
 or
 .B until
-keyword,
+reserved word,
 part of the test in an
 .I if
 statement, part of a command executed in a
index 55c4d3c4eb12c3696087f3b7d5f3501908c002fd..6afc4dc27f794737657efda33e364184aa74078b 100644 (file)
@@ -1769,7 +1769,7 @@ The null string is a valid value.
 Once a variable is set, it may be unset only by using
 the @code{unset} builtin command.
 
-A variable may be assigned to by a statement of the form
+A variable is assigned to using a statement of the form
 @example
 @var{name}=[@var{value}]
 @end example
@@ -2325,6 +2325,14 @@ $ : $@{var=DEFAULT@}
 $ echo $var
 DEFAULT
 $ var=
+$ : $@{var=DEFAULT@}
+$ echo $var
+
+$ var=
+$ : $@{var:=DEFAULT@}
+$ echo $var
+DEFAULT
+$ unset var
 $ : $@{var:=DEFAULT@}
 $ echo $var
 DEFAULT
@@ -2345,6 +2353,16 @@ Otherwise, the value of @var{parameter} is substituted.
 $ var=
 $ : $@{var:?var is unset or null@}
 bash: var: var is unset or null
+$ echo $@{var?var is unset@}
+
+$ unset var
+$ : $@{var?var is unset@}
+bash: var: var is unset
+$ : $@{var:?var is unset or null@}
+bash: var: var is unset or null
+$ var=123
+$ echo $@{var:?var is unset or null@}
+123
 @end example
 
 @item $@{@var{parameter}:+@var{word}@}
@@ -2357,9 +2375,18 @@ The value of @var{parameter} is not used.
 $ var=123
 $ echo $@{var:+var is set and not null@}
 var is set and not null
+$ echo $@{var+var is set@}
+var is set
 $ var=
 $ echo $@{var:+var is set and not null@}
 
+$ echo $@{var+var is set@}
+var is set
+$ unset var
+$ echo $@{var+var is set@}
+
+$ echo $@{var:+var is set and not null@}
+
 $ 
 @end example
 
@@ -4751,7 +4778,8 @@ command), a list, or a compound command returns a
 non-zero exit status,
 subject to the following conditions.
 The @code{ERR} trap is not executed if the failed command is part of the
-command list immediately following an @code{until} or @code{while} keyword,
+command list immediately following an
+@code{until} or @code{while} reserved word,
 part of the test following the @code{if} or @code{elif} reserved words,
 part of a command executed in a @code{&&} or @code{||} list
 except the command following the final @code{&&} or @code{||},
@@ -5889,7 +5917,8 @@ a list (@pxref{Lists}),
 or a compound command (@pxref{Compound Commands})
 returns a non-zero status.
 The shell does not exit if the command that fails is part of the
-command list immediately following a @code{while} or @code{until} keyword,
+command list immediately following a
+@code{while} or @code{until} reserved word,
 part of the test in an @code{if} statement,
 part of any command executed in a @code{&&} or @code{||} list except
 the command following the final @code{&&} or @code{||},
@@ -9279,7 +9308,7 @@ double-quoted string, even if the @code{histexpand} option is enabled.
 
 @item
 When printing shell function definitions (e.g., by @code{type}), Bash does
-not print the @code{function} keyword unless necessary.
+not print the @code{function} reserved word unless necessary.
 
 @item
 Non-interactive shells exit if a syntax error in an arithmetic expansion
@@ -9420,6 +9449,13 @@ separated by spaces, without the @samp{SIG} prefix.
 The @code{kill} builtin does not accept signal names with a @samp{SIG}
 prefix.
 
+@item
+The @code{kill} builtin returns a failure status if any of the pid or job
+arguments are invalid or if sending the specified signal to any of them
+fails.
+In default mode, @code{kill} returns success if the signal was
+successfully sent to any of the specified processes.
+
 @item
 The @code{printf} builtin uses @code{double} (via @code{strtod}) to convert
 arguments corresponding to floating point conversion specifiers, instead of
@@ -10918,7 +10954,7 @@ Bash implements command aliases and the @code{alias} and @code{unalias}
 builtins (@pxref{Aliases}).
 
 @item
-Bash implements the @code{!} keyword to negate the return value of
+Bash implements the @code{!} reserved word to negate the return value of
 a pipeline (@pxref{Pipelines}).
 This is very useful when an @code{if} statement needs to act only if a
 test fails.
index 021bd4ff3de5c595c21506caecb593cca285a625..1abeafa9054d955fede4b5b11e5f3624159c17a3 100644 (file)
@@ -2,10 +2,10 @@
 Copyright (C) 1988-2025 Free Software Foundation, Inc.
 @end ignore
 
-@set LASTCHANGE Wed Jan  8 09:27:44 EST 2025
+@set LASTCHANGE Tue Feb 11 11:04:12 EST 2025
 
 @set EDITION 5.3
 @set VERSION 5.3
 
-@set UPDATED 8 January 2025
-@set UPDATED-MONTH January 2025
+@set UPDATED 11 February 2025
+@set UPDATED-MONTH February 2025
index 2a8d77876d94a710a26b5898621807024a315635..650628b9cb286834eb09be58676fd361201d60d9 100644 (file)
@@ -103,8 +103,8 @@ INC = -I. -I.. -I$(topdir) -I$(topdir)/lib -I$(topdir)/builtins -I${srcdir} \
 ALLPROG = print truefalse sleep finfo logname basename dirname fdflags \
          tty pathchk tee head mkdir rmdir mkfifo mktemp printenv id whoami \
          uname sync push ln unlink realpath strftime mypid setpgid seq rm \
-         accept csv dsv cut stat getconf kv strptime
-OTHERPROG = necho hello cat pushd asort fltexpr
+         accept csv dsv cut stat getconf kv strptime chmod
+OTHERPROG = necho hello cat pushd asort fltexpr 
 
 SUBDIRS = perl
 
@@ -238,6 +238,9 @@ strftime:   strftime.o
 strptime:      strptime.o
        $(SHOBJ_LD) $(SHOBJ_LDFLAGS) $(SHOBJ_XLDFLAGS) -o $@ strptime.o $(SHOBJ_LIBS)
 
+chmod: chmod.o
+       $(SHOBJ_LD) $(SHOBJ_LDFLAGS) $(SHOBJ_XLDFLAGS) -o $@ chmod.o $(SHOBJ_LIBS)
+
 mypid: mypid.o
        $(SHOBJ_LD) $(SHOBJ_LDFLAGS) $(SHOBJ_XLDFLAGS) -o $@ mypid.o $(SHOBJ_LIBS)
 
@@ -319,7 +322,7 @@ OBJS = print.o truefalse.o accept.o sleep.o finfo.o getconf.o logname.o \
        basename.o dirname.o tty.o pathchk.o tee.o head.o rmdir.o necho.o \
        hello.o cat.o csv.o dsv.o kv.o cut.o printenv.o id.o whoami.o uname.o \
        sync.o push.o mkdir.o mktemp.o realpath.o strftime.o setpgid.o stat.o \
-       fdflags.o seq.o asort.o strptime.o        
+       fdflags.o seq.o asort.o strptime.o chmod.o
 
 ${OBJS}:       ${BUILD_DIR}/config.h
 
@@ -340,6 +343,7 @@ rmdir.o: rmdir.c
 necho.o: necho.c
 hello.o: hello.c
 cat.o: cat.c
+chmod.o: chmod.c
 csv.o: csv.c
 dsv.o: dsv.c
 kv.o: kv.c
diff --git a/examples/loadables/chmod.c b/examples/loadables/chmod.c
new file mode 100644 (file)
index 0000000..dc7ca73
--- /dev/null
@@ -0,0 +1,156 @@
+/* chmod - change file mode bits */
+
+/* See Makefile for compilation details. */
+
+/*
+   Copyright (C) 2024 Free Software Foundation, Inc.
+
+   This file is part of GNU Bash.
+   Bash is free software: you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation, either version 3 of the License, or
+   (at your option) any later version.
+
+   Bash is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with Bash.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <config.h>
+
+#include "bashtypes.h"
+#include "posixstat.h"
+#include <errno.h>
+#include <stdio.h>
+#include "bashansi.h"
+#if defined (HAVE_UNISTD_H)
+#  include <unistd.h>
+#endif
+
+#include "builtins.h"
+#include "shell.h"
+#include "bashgetopt.h"
+#include "common.h"
+
+#if !defined (errno)
+extern int errno;
+#endif
+
+#define ISOCTAL(c)     ((c) >= '0' && (c) <= '7')
+
+extern int parse_symbolic_mode (char *, mode_t);
+
+#define STANDARD_BITS  (S_IRWXU | S_IRWXG | S_IRWXO)
+#define ALLBITS (STANDARD_BITS | S_ISUID| S_ISGID | S_ISVTX)
+
+int
+chmod_builtin (WORD_LIST *list)
+{
+  int opt, nmode, lmode, rval;
+  char *mode;
+  struct stat st;
+  WORD_LIST *l;
+
+  reset_internal_getopt ();
+  mode = (char *)NULL;
+  while ((opt = internal_getopt(list, "fhvRHLP")) != -1)
+    switch (opt)
+      {
+       CASE_HELPOPT;
+       default:
+         return (EX_DISKFALLBACK);
+      }
+  list = loptend;
+
+  if (list == 0)
+    {
+      builtin_usage ();
+      return (EX_USAGE);
+    }
+
+  mode = list->word->word;
+  list = list->next;
+
+  if (list == 0)
+    {
+      builtin_usage ();
+      return (EX_USAGE);
+    }
+
+  nmode = -1;
+  if (ISOCTAL (*mode)) /* octal number */
+    {
+      nmode = read_octal (mode);
+      if (nmode < 0)
+       {
+         builtin_error ("invalid file mode: %s", mode);
+         return (EXECUTION_FAILURE);
+       }
+    }
+  else                                 /* test for valid symbolic mode */
+    {
+      /* initial bits are a=rwx; the mode argument modifies them */
+      lmode = parse_symbolic_mode (mode, ALLBITS);
+      if (lmode < 0)
+       {
+         builtin_error ("invalid file mode: %s", mode);
+         return (EXECUTION_FAILURE);
+       }
+    }
+
+  for (rval = EXECUTION_SUCCESS, l = list; l; l = l->next)
+    {
+      lmode = nmode;
+      if (stat (l->word->word, &st) < 0)
+       {
+         builtin_error ("`%s': cannot stat: %s", l->word->word, strerror (errno));
+         rval = EXECUTION_FAILURE;
+         continue;
+       }
+
+      if (lmode == -1)
+       {
+         lmode = parse_symbolic_mode (mode, st.st_mode & ALLBITS);
+         if (lmode < 0)
+           {
+             builtin_error ("`%s': invalid file mode: %s", l->word->word, mode);
+             rval = EXECUTION_FAILURE;
+             continue;
+           }
+       }
+
+      if (chmod (l->word->word, lmode))
+       {
+         builtin_error ("`%s': cannot change mode: %s", l->word->word, strerror (errno));
+         rval = EXECUTION_FAILURE;
+         continue;
+       }
+    }
+  return rval;
+}
+
+char *chmod_doc[] = {
+       "Change file mode bits.",
+       "",
+       "Change file mode bits.  Change the mode bits of files named as",
+       "arguments, in the order specified, as specified by MODE."
+       "The MODE argument may be an octal number or a symbolic mode like",
+       "that described in chmod(1).  If a symbolic mode is used, the",
+       "operations are interpreted relative to an initial mode of \"a=rwx\".",
+       "",
+       "The return value is 0 unless an error occurs.",
+       (char *)NULL
+};
+
+struct builtin chmod_struct = {
+       "chmod",
+       chmod_builtin,
+       BUILTIN_ENABLED,
+       chmod_doc,
+       "chmod [-R] mode file [file...]",
+       0
+};
index 50df6c104acb348fc7530cd2b398ebc8cfd27c25..03931c6c015bacf4f45d22556da4bbd0f131d800 100644 (file)
@@ -4202,7 +4202,7 @@ execute_null_command (REDIRECT *redirects, int pipe_in, int pipe_out, int async)
     {
       forcefork += rd->rflags & REDIR_VARASSIGN;
       /* Safety */
-      forcefork += (rd->redirector.dest == 0 || fd_is_bash_input (rd->redirector.dest)) && (INPUT_REDIRECT (rd->instruction) || TRANSLATE_REDIRECT (rd->instruction) || rd->instruction == r_close_this);
+      forcefork += (rd->redirector.dest == 0 || fd_is_bash_input (rd->redirector.dest));
     }
 
   if (forcefork || pipe_in != NO_PIPE || pipe_out != NO_PIPE || async)
diff --git a/input.c b/input.c
index ad1ebefdb1d375e117daa150468724edb1e78eb7..9995b94f8aec332a85f2e8d6d973494ea2a98e8f 100644 (file)
--- a/input.c
+++ b/input.c
@@ -302,10 +302,15 @@ save_bash_input (int fd, int new_fd)
 int
 check_bash_input (int fd)
 {
+  int nfd;
+
   if (fd_is_bash_input (fd))
     {
       if (fd > 0)
-       return ((save_bash_input (fd, -1) == -1) ? -1 : 0);
+       {
+         nfd = save_bash_input (fd, -1);       /* allocates new fd */
+         return (nfd);
+       }
       else if (fd == 0)
         return ((sync_buffered_stream (fd) == -1) ? -1 : 0);
     }
diff --git a/redir.c b/redir.c
index 6e950f8469b300eeb584060f83053e3d00b8a552..98137492ca7795d289f2fb1e8160ae04ffe1665f 100644 (file)
--- a/redir.c
+++ b/redir.c
@@ -95,6 +95,7 @@ extern REDIRECT *exec_redirection_undo_list;
 static void add_exec_redirect (REDIRECT *);
 static int add_undo_redirect (int, enum r_instruction, int);
 static int add_undo_close_redirect (int);
+static int add_undo_fd_redirect (int, int);
 static int expandable_redirection_filename (REDIRECT *);
 static int stdin_redirection (enum r_instruction, int);
 static int undoablefd (int);
@@ -761,7 +762,7 @@ static int
 do_redirection_internal (REDIRECT *redirect, int flags, char **fnp)
 {
   WORD_DESC *redirectee;
-  int redir_fd, fd, redirector, r, oflags, rflags;
+  int redir_fd, fd, redirector, r, oflags, rflags, fdactive;
   intmax_t lfd;
   char *redirectee_word;
   enum r_instruction ri;
@@ -882,6 +883,8 @@ do_redirection_internal (REDIRECT *redirect, int flags, char **fnp)
       dispose_redirects (new_redirect);
     }
 
+  fdactive = 0;
+
   switch (ri)
     {
     case r_output_direction:
@@ -947,14 +950,17 @@ do_redirection_internal (REDIRECT *redirect, int flags, char **fnp)
              if (fd != redirector && (redirect->rflags & REDIR_VARASSIGN) && varassign_redir_autoclose)
                r = add_undo_close_redirect (redirector);             
              else if ((fd != redirector) && (fcntl (redirector, F_GETFD, 0) != -1))
-               r = add_undo_redirect (redirector, ri, -1);
+               {
+                 fdactive = 1;
+                 r = add_undo_redirect (redirector, ri, -1);
+               }
              else
                r = add_undo_close_redirect (redirector);
              REDIRECTION_ERROR (r, errno, fd);
            }
 
          /* inhibit call to sync_buffered_stream() for async processes */
-         if (redirector != 0 || (subshell_environment & SUBSHELL_ASYNC) == 0)
+         if ((redirector != 0 || (subshell_environment & SUBSHELL_ASYNC) == 0) && (flags & RX_UNDOABLE))
            check_bash_input (redirector);
 
          /* Make sure there is no pending output before we change the state
@@ -1062,13 +1068,17 @@ do_redirection_internal (REDIRECT *redirect, int flags, char **fnp)
                  if (fd != redirector && (redirect->rflags & REDIR_VARASSIGN) && varassign_redir_autoclose)
                    r = add_undo_close_redirect (redirector);         
                  else if ((fd != redirector) && (fcntl (redirector, F_GETFD, 0) != -1))
-                   r = add_undo_redirect (redirector, ri, -1);
+                   {
+                     fdactive = 1;
+                     r = add_undo_redirect (redirector, ri, -1);
+                   }
                  else
                    r = add_undo_close_redirect (redirector);
                  REDIRECTION_ERROR (r, errno, fd);
                }
 
-             check_bash_input (redirector);
+             if (flags & RX_UNDOABLE)
+               check_bash_input (redirector);
 
              if (redirect->rflags & REDIR_VARASSIGN)
                {
@@ -1120,7 +1130,10 @@ do_redirection_internal (REDIRECT *redirect, int flags, char **fnp)
              if ((redirect->rflags & REDIR_VARASSIGN) && varassign_redir_autoclose)
                r = add_undo_close_redirect (redirector);             
              else if (fcntl (redirector, F_GETFD, 0) != -1)
-               r = add_undo_redirect (redirector, ri, redir_fd);
+               {
+                 fdactive = 1;
+                 r = add_undo_redirect (redirector, ri, redir_fd);
+               }
              else
                r = add_undo_close_redirect (redirector);
              REDIRECTION_ERROR (r, errno, -1);
@@ -1137,7 +1150,7 @@ do_redirection_internal (REDIRECT *redirect, int flags, char **fnp)
            }
 
          /* inhibit call to sync_buffered_stream() for async processes */
-         if (redirector != 0 || (subshell_environment & SUBSHELL_ASYNC) == 0)
+         if ((redirector != 0 || (subshell_environment & SUBSHELL_ASYNC) == 0) && (flags & RX_UNDOABLE))
            check_bash_input (redirector);
 
          if (redirect->rflags & REDIR_VARASSIGN)
@@ -1221,7 +1234,7 @@ do_redirection_internal (REDIRECT *redirect, int flags, char **fnp)
          xtrace_fdchk (redirector);
 
          /* inhibit call to sync_buffered_stream() for async processes */
-         if (redirector != 0 || (subshell_environment & SUBSHELL_ASYNC) == 0)
+         if ((redirector != 0 || (subshell_environment & SUBSHELL_ASYNC) == 0) && (flags & RX_UNDOABLE))
            check_bash_input (redirector);
          r = close_buffered_fd (redirector);
 
@@ -1354,6 +1367,22 @@ add_undo_close_redirect (int fd)
   return 0;
 }
 
+static int
+add_undo_fd_redirect (int sfd, int rfd)
+{
+  REDIRECTEE rd, sd;
+  REDIRECT *nr;
+
+  sd.dest = sfd;
+  rd.dest = rfd;
+  nr = make_redirection (sd, r_move_input, rd, 0);
+  nr->rflags |= RX_INTERNAL;
+  nr->next = redirection_undo_list;
+  redirection_undo_list = nr;
+
+  return 0;
+}
+
 static void
 add_exec_redirect (REDIRECT *dummy_redirect)
 {