/*
* This file is a part of Alchemy OS project.
* Copyright (C) 2011-2014, Sergey Basalaev <sbasalaev@gmail.com>
*
* This program 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.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*/
package alchemy.apps;
import alchemy.io.IO;
import alchemy.io.UTFReader;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import alchemy.fs.Filesystem;
import alchemy.io.ConnectionInputStream;
import alchemy.io.TerminalInput;
import alchemy.system.NativeApp;
import alchemy.system.Process;
import alchemy.util.ArrayList;
import alchemy.util.Strings;
/**
* Native shell.
* @author Sergey Basalaev
*/
public class Shell extends NativeApp {
static private final String C_USAGE = "Usage: sh -c <cmd> <args>...";
public Shell() { }
public int main(Process p, String[] args) {
try {
int exitcode = 0;
InputStream scriptinput;
if (args.length == 0) {
scriptinput = p.stdin;
} else if (args[0].equals("-c")) {
if (args.length < 2) {
IO.println(p.stderr, C_USAGE);
return -1;
}
StringBuffer cmdline = new StringBuffer(args[1]);
for (int i=2; i<args.length; i++) {
cmdline.append(' ').append(args[i]);
}
scriptinput = new ByteArrayInputStream(Strings.utfEncode(cmdline.toString()));
p.addConnection(new ConnectionInputStream(scriptinput));
} else {
String file = p.toFile(args[0]);
byte[] buf = new byte[(int)Filesystem.size(file)];
InputStream in = Filesystem.read(p.toFile(args[0]));
in.read(buf);
in.close();
scriptinput = new ByteArrayInputStream(buf);
p.addConnection(new ConnectionInputStream(scriptinput));
}
if (p.stdin instanceof TerminalInput) {
((TerminalInput)p.stdin).setPrompt(p.getCurrentDirectory()+'>');
}
UTFReader r = new UTFReader(scriptinput);
while (true) try {
String line = r.readLine();
if (line == null) break;
line = line.trim();
if (line.length() == 0) continue;
if (line.charAt(0) == '#') continue;
ShellCommand cc = null;
try { cc = split(line); }
catch (IllegalArgumentException e) {
IO.println(p.stderr, r.lineNumber()+':'+e.getMessage());
if (scriptinput instanceof TerminalInput) continue;
else return 1;
}
if (cc.cmd.equals("exit")) {
if (cc.args.length > 0) {
try {
exitcode = Integer.parseInt(cc.args[0]);
} catch (NumberFormatException e) {
IO.println(p.stderr, "exit: Not a number: "+cc.args[0]);
exitcode = 1;
}
} else {
exitcode = 0;
}
return exitcode;
} else if (cc.cmd.equals("cd")) {
if (cc.args.length > 0) {
String newdir = p.toFile(cc.args[0]);
p.setCurrentDirectory(newdir);
if (p.stdin instanceof TerminalInput) {
((TerminalInput)p.stdin).setPrompt(p.getCurrentDirectory()+'>');
}
exitcode = 0;
} else {
IO.println(p.stderr, "cd: no directory specified");
exitcode = 1;
}
} else if (cc.cmd.equals("cls")) {
if (p.stdin instanceof TerminalInput) {
((TerminalInput)p.stdin).clear();
}
exitcode = 0;
} else {
Process child = new Process(p, cc.cmd, cc.args);
if (cc.in != null) {
child.stdin = Filesystem.read(p.toFile(cc.in));
}
if (cc.out != null) {
String outfile = p.toFile(cc.out);
if (cc.appendout) {
child.stdout = Filesystem.append(outfile);
} else {
child.stdout = Filesystem.write(outfile);
}
}
if (cc.err != null) {
String errfile = p.toFile(cc.err);
if (cc.appenderr) {
child.stderr = Filesystem.append(errfile);
} else {
child.stderr = Filesystem.write(errfile);
}
}
if (p.stdin instanceof TerminalInput) {
((TerminalInput)p.stdin).setPrompt("");
}
exitcode = child.start().waitFor();
if (cc.in != null) child.stdin.close();
if (cc.out != null) child.stdout.close();
if (cc.err != null) child.stderr.close();
if (child.getError() != null) {
IO.println(p.stderr, child.getError());
}
if (p.stdin instanceof TerminalInput) {
((TerminalInput)p.stdin).setPrompt(p.getCurrentDirectory()+'>');
}
}
} catch (Throwable t) {
IO.println(p.stderr, t);
if (p.stdin instanceof TerminalInput) {
((TerminalInput)p.stdin).setPrompt(p.getCurrentDirectory()+'>');
}
}
return exitcode;
} catch (Exception e) {
//e.printStackTrace();
IO.println(p.stderr, e);
return 1;
}
}
private static final int MODE_CMD = 0;
private static final int MODE_ARG = 1;
private static final int MODE_QARG = 2;
private static final int MODE_IN = 3;
private static final int MODE_OUT_W = 4;
private static final int MODE_OUT_A = 5;
private static final int MODE_ERR_W = 6;
private static final int MODE_ERR_A = 7;
private ShellCommand split(String line) throws IllegalArgumentException {
ShellCommand cc = new ShellCommand();
ArrayList argv = new ArrayList();
int mode = MODE_CMD;
while (line.length() > 0) {
int end;
String token;
if (line.charAt(0) == '\'') {
end = line.indexOf('\'', 1);
if (end < 0) throw new IllegalArgumentException("Unclosed '");
token = line.substring(1,end);
line = line.substring(end+1).trim();
if (mode == MODE_ARG) mode = MODE_QARG;
} else if (line.charAt(0) == '"') {
end = line.indexOf('"', 1);
if (end < 0) throw new IllegalArgumentException("Unclosed \"");
token = line.substring(1, end);
line = line.substring(end+1).trim();
if (mode == MODE_ARG) mode = MODE_QARG;
} else if (line.charAt(0) == '#') {
break;
} else {
end = line.indexOf(' ');
if (end < 0) end = line.length();
token = line.substring(0,end);
line = line.substring(end).trim();
}
if (token.length() == 0) continue;
switch (mode) {
case MODE_CMD:
cc.cmd = token;
mode = MODE_ARG;
break;
case MODE_IN:
cc.in = token;
mode = MODE_ARG;
break;
case MODE_OUT_W:
cc.out = token;
cc.appendout = false;
mode = MODE_ARG;
break;
case MODE_OUT_A:
cc.out = token;
cc.appendout = true;
mode = MODE_ARG;
break;
case MODE_ERR_W:
cc.err = token;
cc.appenderr = false;
mode = MODE_ARG;
break;
case MODE_ERR_A:
cc.err = token;
cc.appenderr = true;
mode = MODE_ARG;
break;
case MODE_ARG:
if (token.equals(">") || token.equals("1>")) mode = MODE_OUT_W;
else if (token.equals(">>") || token.equals("1>>")) mode = MODE_OUT_A;
else if (token.equals("2>")) mode = MODE_ERR_W;
else if (token.equals("2>>")) mode = MODE_ERR_A;
else if (token.equals("<")) mode = MODE_IN;
else argv.add(token);
break;
case MODE_QARG:
argv.add(token);
mode = MODE_ARG;
}
}
String[] args = new String[argv.size()];
for (int i=0; i<argv.size(); i++) {
args[i] = argv.get(i).toString();
}
cc.args = args;
return cc;
}
private static class ShellCommand {
public String cmd;
public String[] args;
public String in;
public String out;
public boolean appendout;
public String err;
public boolean appenderr;
}
}