Browse Source

Shell: Parse and correctly evaluate 'command &' and 'command &&'

This commit adds the InBackground and ShortCircuitOnFailure attributes
to commands, which respectively cause the command to be run in the
background, and abort the command chain with exit status != 0.
AnotherTest 5 years ago
parent
commit
c23c354779
3 changed files with 70 additions and 5 deletions
  1. 27 2
      Shell/Parser.cpp
  2. 8 1
      Shell/Parser.h
  3. 35 2
      Shell/Shell.cpp

+ 27 - 2
Shell/Parser.cpp

@@ -49,11 +49,11 @@ void Parser::commit_subcommand()
     m_subcommands.append({ move(m_tokens), move(m_redirections), {} });
 }
 
-void Parser::commit_command()
+void Parser::commit_command(Attributes attributes)
 {
     if (m_subcommands.is_empty())
         return;
-    m_commands.append({ move(m_subcommands) });
+    m_commands.append({ move(m_subcommands), attributes });
 }
 
 void Parser::do_pipe()
@@ -109,6 +109,31 @@ Vector<Command> Parser::parse()
                 commit_command();
                 break;
             }
+            if (ch == '&') {
+                commit_token(Token::Special);
+
+                if (i + 1 >= m_input.length()) {
+                in_background:;
+                    // Nothing interesting past this token, commit with InBackground
+                    commit_subcommand();
+                    commit_command(Attributes::InBackground);
+                    break;
+                }
+
+                ch = m_input.characters()[++i];
+                ++m_position;
+
+                if (ch == '&') {
+                    // This is '&&', commit with ShortCircuit
+                    commit_subcommand();
+                    commit_command(Attributes::ShortCircuitOnFailure);
+                    break;
+                }
+
+                --i;
+                --m_position;
+                goto in_background;
+            }
             if (ch == '|') {
                 commit_token(Token::Special);
                 if (m_tokens.is_empty()) {

+ 8 - 1
Shell/Parser.h

@@ -45,6 +45,12 @@ struct Token {
     Type type;
 };
 
+enum Attributes {
+    None = 0x0,
+    ShortCircuitOnFailure = 0x1,
+    InBackground = 0x2,
+};
+
 struct Redirection {
     enum Type {
         Pipe,
@@ -71,6 +77,7 @@ struct Subcommand {
 
 struct Command {
     Vector<Subcommand> subcommands;
+    Attributes attributes;
 };
 
 class Parser {
@@ -89,7 +96,7 @@ private:
     };
     void commit_token(Token::Type, AllowEmptyToken = AllowEmptyToken::No);
     void commit_subcommand();
-    void commit_command();
+    void commit_command(Attributes = None);
     void do_pipe();
     void begin_redirect_read(int fd);
     void begin_redirect_write(int fd);

+ 35 - 2
Shell/Shell.cpp

@@ -1052,8 +1052,7 @@ IterationDecision Shell::wait_for_pid(const Shell::SpawnedProcess& process, bool
         if (WEXITSTATUS(wstatus) != 0)
             dbg() << "Shell: " << process.name << ":" << process.pid << " exited with status " << WEXITSTATUS(wstatus);
 
-        if (is_first_command_in_chain)
-            return_value = WEXITSTATUS(wstatus);
+        return_value = WEXITSTATUS(wstatus);
 
         if (job) {
             auto* mutable_job = const_cast<Job*>(job);
@@ -1128,6 +1127,7 @@ ExitCodeOrContinuationRequest Shell::run_command(const StringView& cmd)
                     break;
                 }
             }
+
             dbgprintf("\n");
             for (auto& redirecton : command.subcommands[i].redirections) {
                 for (size_t j = 0; j < i; ++j)
@@ -1151,6 +1151,13 @@ ExitCodeOrContinuationRequest Shell::run_command(const StringView& cmd)
                 }
             }
         }
+        if (auto attributes = command.attributes) {
+            dbgprintf("\n ");
+            if (attributes & Attributes::InBackground)
+                dbgprintf("InBackground ");
+            if (attributes & Attributes::ShortCircuitOnFailure)
+                dbgprintf("ShortCircuitOnFailure ");
+        }
         dbgprintf("\n");
     }
 #endif
@@ -1159,8 +1166,20 @@ ExitCodeOrContinuationRequest Shell::run_command(const StringView& cmd)
     tcgetattr(0, &trm);
 
     int return_value = 0;
+    bool fail_short_circuits = false;
 
     for (auto& command : commands) {
+        if (fail_short_circuits) {
+            if (command.attributes & Attributes::ShortCircuitOnFailure)
+                continue;
+
+            // Do not fail any command after this one, as we've reached the end of a short-circuit chain,
+            // e.g. foo && bar && baz ; foobar
+            //                    ^ we reached this command.
+            fail_short_circuits = false;
+            continue;
+        }
+
         if (command.subcommands.is_empty())
             continue;
 
@@ -1306,6 +1325,14 @@ ExitCodeOrContinuationRequest Shell::run_command(const StringView& cmd)
             dbgprintf("  %d (%s)\n", child.pid, child.name.characters());
 #endif
 
+        if (command.attributes & Attributes::InBackground) {
+            // Set the jobs as running in background and continue without waiting.
+            for (auto& child : children)
+                const_cast<Job*>(jobs.get(child.pid).value())->set_running_in_background(true);
+
+            continue;
+        }
+
         for (size_t i = 0; i < children.size(); ++i) {
             auto& child = children[i];
             dbg() << "Now waiting for " << child.name << " (" << child.pid << ")";
@@ -1314,6 +1341,12 @@ ExitCodeOrContinuationRequest Shell::run_command(const StringView& cmd)
                     break;
             } while (errno == EINTR);
         }
+
+        if (command.attributes & Attributes::ShortCircuitOnFailure) {
+            if (return_value != 0) {
+                fail_short_circuits = true;
+            }
+        }
     }
 
     last_return_code = return_value;