Page MenuHomePhorge

No OneTemporary

diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,324 +1,327 @@
Copyright (C) 2011-2015 Lannocc Technologies
@%@~LICENSE~@%@
+saw_012915_1 - Create TaskData class for encapsulating the data necessary for task execution,
+ thereby simplifying numerous method signatures.
+
saw_012615_1 - Enhancements and fixes:
- User support.
- Rewritten Call exec with improved variable handling.
- Pass-through for content-disposition.
- /bin/set now exports variable to parent session, which is in the caller's scope.
- /bin/cat support for setting content-disposition.
- /bin/echo gets ?noline option.
- MySQL device improvements, including: bulk update (POST); multi-table delete;
?join, ?group, and ?limit options.
- /usr/bin/formfile gets ?quiet option; ?export renamed to ?set.
- /usr/bin/sed now requires the "g" option since it currently only operates as a
global substitution.
- /usr/bin/xlink gets ?quiet option and also allows embedding non-XML resources.
- /usr/bin/xmlout now escapes tag names, adds "tag-name-orig" attribute.
- Add /usr/bin/xmlin (also /bin/xi).
- /usr/bin/xslt passes in extra anonymous arguments as arg1, arg2,... parameters.
saw_082214_1 - More features and fixes:
- Add /bin/exists.
- Add /bin/patch.
- Add /usr/bin/formfile.
- Add /usr/bin/headers.
- Add /usr/bin/test.
- Initial support for hot quote actions (e.g. `echo foo`).
- /bin/export and /bin/set now accept a value from standard input.
- /$ execution now ignores ?iosession parameter (needs redesign).
- Add ?first option to /bin/cat to display command-line resources before standard
input.
- /bin/delete and /bin/put now pass-through any arguments.
- /bin/echo now prints literal newline instead of system default.
- Add double-dash to /bin/system command.
- MySQL device improvements.
- /usr/bin/form2xml now handles multipart/form-data.
- Add daily and specific-time modes to /usr/sbin/cron.
- Add a version of pipe() to Utils that stops when a specified sequence is found.
saw_061014_1 - Many improvements:
- /usr/bin/sed (simple stream editor), uses Java's String.replaceAll ().
- /usr/bin/java2xml and /usr/bin/xml2java (dependency on XStream).
- /sbin/cron for simple command scheduling.
- Shell session instance management slightly improved and added concept of a
'master' session.
- New (optional) init system and implementation via Init servlet (/sbin/init),
init configuration at /etc/inittab, and scripts at /etc/rc and /etc/environment.
- Add support for 'iovar.sessions', 'iovar.log.level', and 'iovar.log.file'
context parameters.
- Log and Init servlets are specified explicitly in web.xml to ensure load on
startup.
- WebBuilder Ant task adds optional support for a starting <load-on-startup> value
that all automatic servlet entries can be given incrementally.
- Http transport now supports PATCH method (dependency on Apache httpclient).
- Better handling and pass-through of content-type (still not complete).
- Misc. bug fixes.
saw_050914_1 - /bin/help.
saw_050814_10 - Transport handlers now extend HttpServlet and behave as shell utilities. This
allows pluggable handlers to work when WAR-packaged and lacking the original
symlinks.
saw_050814_9 - Clean out more old files.
saw_050814_8 - Updated INSTALL file for Google Code.
saw_050814_7 - Minor INSTALL / README file updates.
saw_050814_6 - Add LICENSE (MIT). Add sample.project.xml for Netbeans.
saw_050814_5 - Add sample.build.properties and sample.app.properties.
saw_050814_4 - Add /bin/iosh as another link to the IOVAR shell. Scripts that cannot or should not
be used in BASH now reference /bin/iosh instead of /bin/sh (or /bin/bash, etc.).
saw_050814_3 - Preparing for beta release: removing old and extra stuff.
saw_050814_2 - More build.xml fixes.
saw_050814_1 - Cleaning up build.xml. Ant 'project' task now builds a WAR (deployment not tested).
saw_050214_1 - /usr/bin/xlink now copies any additional attributes onto root node.
saw_042714_2 - XML Copy now adds prefix mappings as necessary.
saw_042714_1 - /usr/bin/xlink.
saw_042514_1 - /dev/buffer.
saw_042414_6 - Simply /dev/null.
saw_042414_5 - /bin/system will now pass other named parameters as environment variables.
saw_042414_4 - Escaped shell graph item now properly handles \n, \r, and \t. /bin/system now
passes input to the system process.
saw_042414_3 - Shell parameter handling (?x=y) now properly handles multiple assignments for the
same parameter name.
saw_042414_2 - MySql ?follow support for getList also.
saw_042414_1 - Add ?follow parameter to MySql driver for following selected columns.
saw_042114_13 - Add ?where and ?order parameters to MySql driver when retrieving a list.
/usr/bin/xmlout now (roughly) handles any input passed in and inserts the new
node(s) in the top of the document.
saw_042114_12 - Links aren't properly handled when they're pointing to folders (need to work it out
later).
saw_042114_11 - The beginnings of proper redirect handling (no longer do we allow HttpURLConnection
to follow them automatically). Right now only Call actually properly follows a
redirect on invocation.
saw_042114_10 - /bin/system now looks for colon in command arguments and interprets as a local
transport handler (still very hacky).
saw_042114_9 - Fix problem when variable assignment is the only command in an operation.
saw_042114_8 - /bin/system should send process error stream to stdout.
saw_042114_7 - /bin/sh should only try processing POST data if no anonymous args passed in.
saw_042114_6 - /bin/sh will now process POST data as though it were a shell script.
saw_042114_5 - MySql driver now supports DELETE.
saw_042114_4 - MySql driver needs to quote column names when doing insert.
saw_042114_3 - MySql driver now does insert in PUT / (instead of POST).
saw_042114_2 - Make sure table name is quoted when getting description in MySql driver.
saw_042114_1 - MySql driver now differentiates between request for directory listing (ending with
slash) and request for new item template (no slash).
saw_041914_7 - Fix problem of Background ('&') gobbling up '&&'.
saw_041914_6 - /bin/sleep.
saw_041914_5 - Basic job control.
saw_041914_4 - /bin/system - execute system call. Add file: transport support.
saw_041914_3 - MySql driver and /usr/bin/form2xml now add "tag-name-orig" attribute if the tag
name had to be modified to follow XML spec.
saw_041914_2 - Some basic column name sanitation for XML tag name in MySql driver.
saw_041914_1 - Remove the "iolink" init-parameter hack; use getServletPath () instead.
saw_041714_8 - /usr/bin/form2xml - converts standard input URLEncoded data into simple xml tree.
saw_041714_7 - /usr/bin/get now passes along any parameters. MySql and PostreSQL drivers will now
fetch an optional list of columns when requesting a list.
saw_041714_6 - /usr/bin/xslt now passes named parameters to stylesheets. Created /usr/bin/setxsl
for easily adding <?xsl-stylesheet?> instructions to XMl documents.
Scripts can now utilize standard input (passed automatically to the first
operation).
saw_041714_5 - Make sure session gets persisted when variable assignment happens.
saw_041714_4 - Variable assignments as part of an operation work again (instead of only via the
export command).
saw_041714_3 - Default session handler should also execute /etc/shrc.
saw_041714_2 - Now supporting GET requests to $/* as a convenience mechanism to POST to * and
utilize a default session.
saw_041714_1 - /usr/bin/xargs, currently handling application/x-www-form-urlencoded input.
saw_041614_1 - Added Parameter type. Http transport and task Return struct now handle content-type.
/bin/cat is the first to support named parameters with ?help and ?type= options.
saw_041514_3 - Basic implementations for mkdir, rm, rmdir, touch.
saw_041514_2 - Improve the interactive interface.
saw_041514_1 - GraphReader now supports multiple read-ahead (peek at many). Conditional tests have
been moved out of Operation and into Group where they belong, greatly simplifying
and improving logic and nested block handling.
Still some work to do with status code propagation in certain spots where it should
be automatically cleared. E.g. `cat foo || { echo bad && echo good }`.
saw_041414_4 - /bin/which.
saw_041414_3 - All binaries now implement the standard utility doHead, doGet, and doPut methods
(where applicable).
saw_041414_2 - Standard utility doGet () and doPut () look for a corresponding source file in /src
to retrieve or update; /usr/bin/cat is the first to implement this.
saw_041414_1 - Exit codes ($?) now working properly; /usr/bin/cat now sets exit value.
saw_041114_10 - Starting to imlement some basic error checking for HTTP GET. See the big note in
Cat.java for what needs to be done next for proper return code $? variable usage.
saw_041114_9 - Implemented And command ("&&"). Fixed compile error from previous commit.
saw_041114_8 - Basic status support; implemented Or command ("||"). Removed legacy task parser.
saw_041114_7 - Interactive mode now automatically sets focus to the command input.
saw_041114_6 - Echo will now output its input first before any arguments, just like Cat.
saw_041114_5 - Partial support for HTTP PATCH (append).
saw_041114_4 - Fixes to parser text handling.
saw_041114_3 - Misc./minor.
saw_041114_2 - Minor visual improvements to the interactive interface.
saw_041114_1 - Path changes (working on debussy... need to get installation-specific settings out
of source control).
saw_101613_1 - Misc. fixes incorporating new parser.
saw_101213_1 - New Graph/Task parser now working with I/O redirection and chaining, and supporting
enough features to replace the old parser.
saw_100613_2 - New Operation Graph now successfully assembles with multiple chain, input, and
output redirection, though only a single call will currently execute (work in
progress).
saw_100613_1 - New Graph/Task parser hooked up to shell (not all features implemented yet).
saw_100313_1 - Fix spelling in comments.
saw_100213_2 - New parser work.
saw_100213_1 - New parser, using Graph and Task interface model (work in progress).
saw_041013_1 - Simple PostgreSQL device (still needs work to handle updates properly).
saw_021713_1 - Simple MySQL device.
saw_021613_1 - /usr/bin/xmlout (aka /usr/bin/xo). Fix WebBuilder task to only add servlet entries
once when multiple links to the same servlet are found.
saw_012013_1 - /bin/exit and /bin/status (work in progress).
saw_011313_2 - Misc. cleanup.
saw_011313_1 - Transport handlers now installed in /dev/trans.
saw_011113_5 - Basic i/o redirection support ( < and > ).
saw_011113_4 - Improved output of /bin/set for local variables.
saw_011113_3 - /usr/bin/basename.
saw_011113_2 - Calls are now invoked with local argument variables $#, $@, $1, $2, $3, etc.
saw_011113_1 - Basic inline variable assignment (e.g. $ foo=bar ./do_something).
saw_011013_8 - Bug fixes in Utils and Local.
saw_011013_7 - /usr/bin/head and /usr/bin/tail. Everything now uses the new Resource.handler ()
approach.
saw_011013_6 - Misc. cleanup/organization.
saw_011013_5 - Convert Cat and Get to new Resource.handler () approach. Utils.pipe () will now call
out.flush ().
saw_011013_4 - Chain command implemented. Misc. bug fixes and improvements.
saw_011013_3 - XSLT processor installed at /usr/bin/xslt.
saw_011013_2 - Remove unused Java files.
saw_011013_1 - Remove Orbeon-related stuff.
saw_010913_1 - Implemented $PATH searching. Interactive mode now runs /etc/shrc to set up $PATH.
saw_010813_2 - Make sure Ant tasks are built by default.
saw_010813_1 - Parser rewritten and greatly simplified.
saw_123012_1 - Split Parser into com.iovar.web.bin.shell.input package. Added Script input type.
saw_122412_1 - Reorganize Parser.java.
saw_122312_1 - /bin/export. Also some work on resource handling and shell session reference
passing.
saw_122212_1 - Symbolic links to servlets now reference the .class file directly instead of using
the 'iojava:' prefix hack.
saw_122112_1 - Add README, INSTALL files.
saw_122012_2 - Proper /bin/cat.
saw_122012_1 - Misc. cleanup/improvements.
saw_121912_2 - Misc. cleanup.
saw_121912_1 - Now includes a basic system logger at /dev/log.
saw_121812_1 - Now depends on Java 1.7. Moved a bunch of stuff around. Implemented filesystem-based
symbolic links for mapping url resources to Java servlets.
saw_012012_2 - Some basic commands now working from the interactive prompt!
saw_012012_1 - Fix Parser bugs. Split Shell interactive move into its own servlet.
saw_102611_1 - Shell and Parser integration. Still need to exclude comment strings.
saw_102511_2 - Hashbang execution support; updating shell for new invocation methods (not complete).
saw_102511_1 - Working get, put (empty), and delete with new anonymous argument syntax (?=).
saw_060411_3 - /bin/cat.
saw_060411_2 - Created timescroll database.
saw_060411_1 - Cloned from usians repository.
diff --git a/src/com/iovar/web/bin/shell/Interactive.java b/src/com/iovar/web/bin/shell/Interactive.java
--- a/src/com/iovar/web/bin/shell/Interactive.java
+++ b/src/com/iovar/web/bin/shell/Interactive.java
@@ -1,191 +1,191 @@
/*
- * Copyright (C) 2011-2014 Lannocc Technologies
+ * Copyright (C) 2011-2015 Lannocc Technologies
* @%@~LICENSE~@%@
*/
package com.iovar.web.bin.shell;
// local imports:
import com.iovar.web.*;
import com.iovar.web.bin.shell.task.*;
import com.iovar.web.dev.*;
import com.iovar.web.proc.*;
// java imports:
import java.net.*;
import java.io.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;
// 3rd-party imports:
/**
* Interactive shell.
*
* @author shawn@lannocc.com
*/
public class Interactive extends HttpServlet
{
public static final String PARAM_SHRC = "shrc";
protected void doHead (final HttpServletRequest req, final HttpServletResponse resp) throws IOException
{
Utils.doHead (this, req, resp);
}
/**
* This is one of the few that doesn't follow the standard Utils.goGet () because
* this is the servlet likely to be called directly from an HTML browser (which have
* limited support for anything but GET).
*/
protected void doGet (final HttpServletRequest req, final HttpServletResponse resp) throws IOException, ServletException
{
//Utils.doGet (this, req, resp);
final String path = req.getPathInfo ();
if (path==null) // run self
{
doPost (req, resp);
}
else // run something else
{
exec (req, resp, path);
}
}
protected void doPut (final HttpServletRequest req, final HttpServletResponse resp) throws IOException
{
Utils.doPut (this, req, resp);
}
public void doPost (final HttpServletRequest req, final HttpServletResponse resp) throws IOException, ServletException
{
final String path = req.getPathInfo ();
if (path==null) // run self
{
if (req.getParameter (Shell.PARAM_CMD)!=null)
{
final Map<String,List<String>> params = Utils.getParams (req.getQueryString ());
Shell.exec (getServletContext (), req.getRemoteUser (), params, req, resp);
}
else
{
interactive (req, resp);
}
}
else // run something else
{
exec (req, resp, path);
}
}
/**
* Interactive mode will utilize shell master session if it exists
* (usually set up via /sbin/init). If none exists it will create a session.
*/
void interactive (final HttpServletRequest req, final HttpServletResponse resp) throws IOException
{
resp.setContentType ("text/html");
final PrintWriter out = resp.getWriter ();
final ServletContext context = getServletContext ();
out.println ("<html>");
out.println (" <head>");
out.println (" <title>interactive shell</title>");
out.println (" <link rel=\"stylesheet\" type=\"text/css\" href=\"usr/include/interactive.css\">");
out.println (" </head>");
out.println (" <body onload=\"document.forms[0].elements['input'].focus()\">");
/*
final Enumeration attrs = req.getSession ().getAttributeNames ();
while (attrs.hasMoreElements ())
{
Log.debug ("http session attribute: " + attrs.nextElement ());
}
*/
Log.debug ("remote user: "+req.getRemoteUser ());
ShellSession.Instance session = Shell.getSession (context);
if (session!=null) Log.debug ("found master session: "+session);
else
{
Log.debug ("creating session");
session = ShellSession.newInstance (context);
}
Log.info ("session: "+session);
/*
String sessions = context.getInitParameter (Shell.SESSIONS_PARAM);
// FIXME: hardcoded session path
if (sessions==null) sessions = "local:/proc/shell/";
final String path = sessions+session.getId ();
session.saveTo (path, context);
*/
session.save (context);
// shrc support (login/interactive shell)
String shrc = req.getParameter (PARAM_SHRC);
if (shrc==null) shrc = "res:/etc/shrc";
if (shrc.length ()>0)
{
final Map<String,List<String>> params = new HashMap<String,List<String>> ();
params.put (Shell.EXT_PARAM_SESSION, Arrays.asList (new String[] { session.getPath () }));
Transport.handler (shrc, params, context).post (null, null);
}
out.println (" <form target=\"output\" method=\"GET\">");
out.println (" <div id=\"session\">");
out.println (" <label>session: <input type=\"text\" name=\""+Shell.PARAM_SESSION+"\" value=\""+session.getPath ()+"\"></label>");
out.println (" <br><a href=\"$/dev/log\" target=\"log\">log</a>");
out.println (" </div>");
out.println (" <p>"+Interactive.class.getName ());
out.println (" <br>"+Shell.class.getName ()+" version: "+Shell.VERSION);
out.println ();
out.println (" <br><br>");
//out.println (" <iframe name=\"log\" width=\"800\" height=\"200\" src=\"dev/log\" scrolling=\"yes\" style=\"overflow:scroll; white-space:nowrap\"> </iframe>");
//out.println (" <br><a href=\"javascript:window.frames[0].location.reload()\" style=\"float:right\">refresh log</a>");
out.println ();
out.println (" <input id=\"cmd_exec\" type=\"submit\" value=\"exec\">");
out.println (" <label id=\"cmd\">$ <input type=\"text\" id=\"cmd_in\" name=\""+Shell.PARAM_CMD+"\" size=\"60\"></label>");
out.println (" </form>");
out.println (" <div id=\"output\"><iframe name=\"output\" width=\"800\" height=\"300\"> </iframe></div>");
out.println (" <script language=\"javascript\"> document.getElementById ('cmd_in').focus (); </script>");
out.println (" </body>");
out.println ("</html>");
}
void exec (final HttpServletRequest req, final HttpServletResponse resp, final String path) throws IOException, ServletException
{
final ServletContext context = getServletContext ();
final InputStream in = req.getInputStream (); // for some reason we have to grab this before getSession ()
final ShellSession.Instance session = Shell.init (context);
Log.debug ("request data contentType: "+req.getContentType ());
final Map<String,List<String>> params = Utils.getParams (req.getQueryString ());
// FIXME: need to completely re-evaluate how the ?iosession param should be used (if at all).
// Currently, iosession is passed automatically by IOVAR calls. However, when calling
// on a remote machine this will not be a valid session. For now, we disable accepting
// any sessions here (relying on master session / cookies instead).
if (params.containsKey (Shell.EXT_PARAM_SESSION))
{
Log.debug ("removing iosession parameter");
params.remove (Shell.EXT_PARAM_SESSION);
}
- final Return r = Shell.exec (path, params, session, context, req.getRemoteUser (), in, req.getContentType ());
+ final Return r = Shell.exec (path, params, new TaskData (session, context, req.getRemoteUser (), in, req.getContentType ()));
Log.debug ("output content type: "+r.type);
resp.setContentType (r.type);
if (r.disposition!=null)
{
resp.setHeader ("Content-Disposition", r.disposition);
}
Utils.pipe (r.data, resp.getOutputStream ());
}
}
diff --git a/src/com/iovar/web/bin/shell/Shell.java b/src/com/iovar/web/bin/shell/Shell.java
--- a/src/com/iovar/web/bin/shell/Shell.java
+++ b/src/com/iovar/web/bin/shell/Shell.java
@@ -1,629 +1,629 @@
/*
- * Copyright (C) 2011-2014 Lannocc Technologies
+ * Copyright (C) 2011-2015 Lannocc Technologies
* @%@~LICENSE~@%@
*/
package com.iovar.web.bin.shell;
// local imports:
import com.iovar.web.Utils;
import com.iovar.web.bin.shell.*;
import com.iovar.web.bin.shell.task.*;
//import com.iovar.web.bin.shell.task.legacy.*;
import com.iovar.web.dev.*;
import com.iovar.web.proc.*;
// java imports:
import java.io.*;
import java.net.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;
// 3rd-party imports:
/**
* Web shell.
*
* @author shawn@lannocc.com
*/
public class Shell extends HttpServlet
{
public static final String VERSION = "1.0-alpha";
public static final String SHELL = "shell";
//static final String INIT_PARAM_PARSER = "parser";
static final String MASTER_SESSION = "_iodefault_";
public static final String EXT_PARAM_SESSION = "iosession";
/** FIXME: SECURITY */
public static final String INT_PARAM_USER = "_iouser_";
static final String PARAM_VERSION = "version";
//static final String PARAM_SESSION = "session";
static final String PARAM_SESSION = EXT_PARAM_SESSION; // FIXME?
//static final String PARAM_SAVE = "save";
static final String PARAM_CMD = "cmd";
protected void doHead (final HttpServletRequest req, final HttpServletResponse resp) throws IOException
{
Utils.doHead (this, req, resp);
}
protected void doGet (final HttpServletRequest req, final HttpServletResponse resp) throws IOException
{
Utils.doGet (this, req, resp);
}
protected void doPut (final HttpServletRequest req, final HttpServletResponse resp) throws IOException
{
Utils.doPut (this, req, resp);
}
public void doPost (final HttpServletRequest req, final HttpServletResponse resp) throws IOException, ServletException
{
final String query = req.getQueryString ();
if (query==null)
{
StringBuilder info = new StringBuilder ();
info.append ("supported parameters:");
info.append (" ").append (PARAM_VERSION);
info.append (" ").append (PARAM_SESSION);
// info.append (" ").append (PARAM_SAVE);
info.append (" ").append (PARAM_CMD);
information (info.toString (), resp);
}
else
{
final Map<String,List<String>> params = Utils.getParams (query);
exec (/*getServletConfig (), */getServletContext (), req.getRemoteUser (), params, req, resp);
}
}
static void exec (/*final ServletConfig config, */final ServletContext context, String user, final Map<String,List<String>> params, final HttpServletRequest req, final HttpServletResponse resp) throws IOException, ServletException
{
// FIXME: SECURITY
if (user==null && params.containsKey (INT_PARAM_USER))
{
user = params.get (INT_PARAM_USER).get (0);
}
/*
for (final Object entry : params.entrySet ())
{
Log.debug ("key: "+((Map.Entry) entry).getKey ());
Log.debug ("val: "+((Map.Entry) entry).getValue ());
}
*/
if (params.containsKey (PARAM_VERSION))
{
information (null, resp);
}
//final PrintWriter out = resp.getWriter ();
final ShellSession.Instance session;
if (params.containsKey (PARAM_SESSION))
{
final List<String> vals = params.get (PARAM_SESSION);
if (vals==null) // create new session
{
Log.info ("create session");
session = ShellSession.newInstance (context);
}
else // load session(s) (merge multiple)
{
ShellSession.Instance s = null;
for (final String path : vals)
{
Log.info ("load session: "+path);
final InputStream in = Transport.handler (path, context).get (); try
{
if (s==null) s = (ShellSession.Instance) Utils.xstream.fromXML (in);
else s = s.merge ((ShellSession.Instance) Utils.xstream.fromXML (in));
/*
ObjectInputStream sin = new ObjectInputStream (in); try
{
if (s==null) s = (ShellSession.Instance) sin.readObject ();
else s = s.merge ((ShellSession.Instance) sin.readObject ());
}
catch (final ClassNotFoundException e)
{
Status.set (resp, resp.SC_INTERNAL_SERVER_ERROR, "cannot deserialize `"+path+"': "+e);
return;
}
finally
{
sin.close ();
}
}
catch (final EOFException e)
{
Status.set (resp, resp.SC_NOT_ACCEPTABLE, "cannot deserialize `"+path+"': "+e);
return;
*/
}
finally
{
in.close ();
}
}
session = s;
}
Log.debug ("session: "+session);
}
else if (params.containsKey (EXT_PARAM_SESSION))
{
session = getSession (req, context);
}
else // no session
{
session = null;
}
/*
if (params.containsKey (PARAM_SAVE))
{
if (session!=null)
{
List<String> locations = params.get (PARAM_SAVE);
if (locations==null) // use default location
{
locations = new ArrayList<String> (1);
final String sessions = context.getInitParameter (SESSIONS_PARAM);
locations.add (sessions+session.getId ());
}
for (final String path : locations)
{
session.saveTo (path, context);
}
}
else
{
Status.set (resp, resp.SC_BAD_REQUEST, "nothing to save");
return;
}
}
*/
if (params.containsKey (PARAM_CMD))
{
final List<String> vals = params.get (PARAM_CMD);
if (vals!=null)
{
for (final String line : vals)
{
Log.debug ("parse: "+line);
//Script task = new Parser ().parse (line);
Group task = new Group (); try
{
if (! task.assemble (new GraphReader (new StringReader (line))))
{
Log.warn ("group did not assemble");
resp.getWriter ().println ("sh: group did not assemble");
task = null;
}
}
catch (final GraphException e)
{
Log.error (e);
resp.getWriter ().println ("sh: "+e);
task = null;
}
Log.info ("task: "+task);
if (task!=null) try
{
// first fork and save child session
// FIXME: figure out a way these don't need to be saved, or could be easily cleaned up
final ShellSession.Instance forked = session.fork (context);
forked.save (context);
/*
String sessions = context.getInitParameter (SESSIONS_PARAM);
// FIXME: hardcoded session path
if (sessions==null) sessions = "local:/proc/shell/";
forked.saveTo (sessions+forked.getId (), context);
*/
//task.exec (forked, context, req, resp, out);
- final Return r = task.exec (forked, context, user, null, null);
+ final Return r = task.exec (new TaskData (forked, context, user));
Log.debug ("cmd returned: "+r);
if (r!=null)
{
Log.debug ("setting content-type: "+r.type);
resp.setContentType (r.type);
Utils.pipe (r.data, resp.getWriter ());
}
}
catch (final IOException e)
{
Log.error (e);
resp.getWriter ().println ("sh: "+e);
}
//resp.getWriter ().println ();
}
}
else // error
{
Status.set (resp, resp.SC_BAD_REQUEST, "missing command line");
}
}
if (params.containsKey (null))
{
final List<String> vals = params.get (null);
if (vals!=null) // execute script(s) (sequentially)
{
boolean first = true;
for (final String script : vals)
{
Log.debug ("script: "+script);
/*
if (first && req.getInputStream ()!=null)
{
Log.debug ("we are first and have data");
final InputStream data = req.getInputStream ();
Log.debug (" XXX: "+(char) data.read ());
Log.debug (" XXX: "+(char) data.read ());
Log.debug (" XXX: "+(char) data.read ());
}
*/
final InputStream sin = Transport.handler (script, context).get ();
/*
HttpURLConnection http = Utils.loopback (context, script);
http.setRequestMethod ("GET");
final InputStream in = http.getInputStream ();
*/
Script task = new Script (); try
{
if (! task.assemble (new GraphReader (new InputStreamReader (sin))))
{
Log.warn ("script did not assemble");
resp.getWriter ().println ("sh: script did not assemble");
task = null;
}
}
catch (final GraphException e)
{
Log.error (e);
resp.getWriter ().println ("sh: "+e);
task = null;
}
Log.info ("task: "+task);
if (task!=null) try
{
// we don't fork the session here because it will already be forked at every
// call within the script
/*
// first fork and save child session
// FIXME: figure out a way these don't need to be saved, or could be easily cleaned up
final ShellSession.Instance forked = session.fork ();
String sessions = config.getInitParameter (INIT_PARAM_SESSIONS);
// FIXME: hardcoded session path
if (sessions==null) sessions = "/proc/shell/";
forked.saveTo (sessions+forked.getId (), context);
task.exec (forked, context, req, resp, out);
*/
//task.exec (session, context, req, resp, out);
- final Return r = task.exec (session, context, user, first ? req.getInputStream () : null, first ? req.getContentType () : null);
+ final Return r = task.exec (new TaskData (session, context, user, first ? req.getInputStream () : null, first ? req.getContentType () : null));
Log.debug ("script returned: "+r);
if (r!=null)
{
Log.debug ("setting content-type: "+r.type);
resp.setContentType (r.type);
if (r.disposition!=null)
{
resp.setHeader ("Content-Disposition", r.disposition);
}
Utils.pipe (r.data, resp.getWriter ());
}
first = false;
}
catch (final IOException e)
{
Log.error (e);
resp.getWriter ().println ("sh: "+e);
}
}
}
else // error
{
Log.error ("missing script resource");
resp.getWriter ().println ("sh: missing script resource");
Status.set (resp, resp.SC_BAD_REQUEST, "missing script resource");
}
}
else // try interpreting stdin as commands to execute
{
final InputStream in = req.getInputStream ();
if (in!=null)
{
Log.debug ("possibly have stdin cmds to execute");
// try interpreting stdin as commands to execute
Group task = new Group (); try
{
final GraphReader gin = new GraphReader (new InputStreamReader (in));
if (! task.assemble (gin))
{
if (gin.peek ()>=0)
{
Log.warn ("stdin group did not assemble");
resp.getWriter ().println ("sh: stdin group did not assemble");
}
task = null;
}
if (task!=null)
{
// we don't fork the session here because it will already be forked at every
// call within the script
/*
// first fork and save child session
// FIXME: figure out a way these don't need to be saved, or could be easily cleaned up
final ShellSession.Instance forked = session.fork ();
String sessions = config.getInitParameter (INIT_PARAM_SESSIONS);
// FIXME: hardcoded session path
if (sessions==null) sessions = "/proc/shell/";
forked.saveTo (sessions+forked.getId (), context);
task.exec (forked, context, req, resp, out);
*/
//task.exec (session, context, req, resp, out);
- final Return r = task.exec (session, context, user, null, null);
+ final Return r = task.exec (new TaskData (session, context, user));
Log.debug ("cmd (stdin) returned: "+r);
if (r!=null)
{
Log.debug ("setting content-type: "+r.type);
resp.setContentType (r.type);
Utils.pipe (r.data, resp.getWriter ());
}
}
}
catch (final GraphException e)
{
Log.error (e);
resp.getWriter ().println ("sh: "+e);
}
catch (final IOException e)
{
Log.error (e);
resp.getWriter ().println ("sh: "+e);
}
}
}
}
/**
* Optional operation that creates and returns a "master" session.
*/
public static ShellSession.Instance init (final ServletContext context) throws IOException
{
Log.debug ("shell init");
//XXX final InputStream in = req.getInputStream (); // for some reason we have to grab this before getSession ()
// String sessions = context.getInitParameter (SESSIONS_PARAM);
// if (sessions==null) sessions = SESSIONS_DEFAULT;
ShellSession.Instance session = getSession (context);
if (session==null)
{
Log.debug ("create session");
session = ShellSession.newInstance (context);
Log.info ("master session created: "+session);
session.save (context);
/*
session.saveTo (sessions+session.getId (), context);
*/
context.setAttribute (MASTER_SESSION, session);
/*
// shrc support
String shrc = req.getParameter (PARAM_SHRC);
if (shrc==null) shrc = "res:/etc/shrc";
if (shrc.length ()>0)
{
final Map<String,List<String>> params = new HashMap<String,List<String>> ();
params.put (Shell.EXT_PARAM_SESSION, Arrays.asList (new String[] { sessions+session.getId () }));
Transport.handler (shrc, params, context).post (null, null);
}
*/
}
else
{
Log.info ("re-using master session: "+session);
}
// session may have changed, reload
// Log.debug ("reloading session: "+session);
// session = (ShellSession.Instance) Utils.xstream.fromXML (Transport.handler (sessions+session.getId (), context).get ());
/*
final ObjectInputStream sin = new ObjectInputStream (Transport.handler (sessions+session.getId (), context).get ()); try
{
session = (ShellSession.Instance) sin.readObject ();
}
catch (final ClassNotFoundException e)
{
Log.error ("while reloading session: "+e);
throw new IOException (e);
}
finally
{
sin.close ();
}
*/
/*
Log.debug ("request data contentType: "+req.getContentType ());
final Return r = Shell.exec (path, Utils.getParams (req.getQueryString ()), session, context, in, req.getContentType ());
Log.debug ("output content type: "+r.type);
resp.setContentType (r.type);
Utils.pipe (r.data, resp.getOutputStream ());
*/
return session;
}
/**
* Get the master session, if it exists.
*/
public static ShellSession.Instance getSession (final ServletContext context)
{
return (ShellSession.Instance) context.getAttribute (MASTER_SESSION);
}
/**
* Execute a given literal command with parameters (no shell processing occurs).
*/
- public static Return exec (final String resource, final Map<String,List<String>> params, final ShellSession.Instance session, final ServletContext context, final String user, final InputStream in, final String contentType) throws IOException, ServletException
+ public static Return exec (final String resource, final Map<String,List<String>> params, final TaskData task) throws IOException, ServletException
{
Log.debug ("shell exec: "+resource);
- Return r = Call.valueOf (resource, params).exec (session, context, user, in, contentType);
+ Return r = Call.valueOf (resource, params).exec (task);
Log.debug ("shell exec returned: "+r);
return r;
}
/**
* @deprecated
*/
public static ShellSession.Instance getSessionFromPost (final HttpServletRequest req) throws IOException
{
return (ShellSession.Instance) Utils.xstream.fromXML (req.getInputStream ());
/*
ObjectInputStream in = new ObjectInputStream (req.getInputStream ());
try
{
return (ShellSession.Instance) in.readObject ();
}
catch (final ClassNotFoundException e)
{
throw new IOException ("failed reading session from POST data", e);
}
*/
}
public static ShellSession.Instance getSession (final String resource, final ServletContext context) throws IOException
{
return (ShellSession.Instance) Utils.xstream.fromXML (Transport.handler (resource, context).get ());
/*
final ObjectInputStream in = new ObjectInputStream (Transport.handler (resource, context).get ());
try
{
return (ShellSession.Instance) in.readObject ();
}
catch (final ClassNotFoundException e)
{
throw new IOException (resource, e);
}
*/
}
public static ShellSession.Instance getSession (final HttpServletRequest req, final ServletContext context) throws IOException
{
//final String resource = req.getParameter (EXT_PARAM_SESSION);
final List<String> resources = Utils.getParams (req.getQueryString ()).get (EXT_PARAM_SESSION);
if (resources==null || resources.size ()<1) {
Log.debug ("no session resource parameter specified: "+EXT_PARAM_SESSION);
return null;
}
return getSession (resources.get (0), context);
}
public static void putSession (final String resource, final ServletContext context, final ShellSession.Instance session) throws IOException
{
final ByteArrayOutputStream bytes = new ByteArrayOutputStream ();
Utils.xstream.toXML (session, bytes);
/*
final ObjectOutputStream out = new ObjectOutputStream (bytes);
out.writeObject (session);
*/
Transport.handler (resource, context).put (new ByteArrayInputStream (bytes.toByteArray ()));
// TODO: check status
}
public static void putSession (final HttpServletRequest req, final ServletContext context, final ShellSession.Instance session) throws IOException
{
//final String resource = req.getParameter (EXT_PARAM_SESSION);
final List<String> resources = Utils.getParams (req.getQueryString ()).get (EXT_PARAM_SESSION);
if (resources==null || resources.size ()<1) throw new IllegalArgumentException ("expecting session parameter");
putSession (resources.get (0), context, session);
}
/**
* Convenience method to set exit value in the current shell session.
*/
public static void exit (final HttpServletRequest req, final ServletContext context, final int code) throws IOException
{
ShellSession.Instance shell = getSession (req, context);
if (shell==null)
{
Log.error ("unable to retrieve session");
throw new IllegalArgumentException ("need a session to set exit value");
}
// FIXME: re-evaluate this... we actually store the exit value in the parent session
shell = shell.getParent (context);
if (shell==null)
{
Log.error ("unable to retrieve parent session");
throw new IllegalArgumentException ("need a parent session to set exit value");
}
if (shell.getExit ()!=null) Log.warn ("session already has an exit value");
shell.setExit (code);
/*
// FIXME: hardcoded path to shell session storage
shell.saveTo ("local:/proc/shell/"+shell.getId (), context);
*/
shell.save (context);
}
static void information (final String message, final HttpServletResponse resp) throws IOException
{
resp.setContentType ("text/plain");
final PrintWriter out = resp.getWriter ();
out.println (Shell.class.getName ());
out.println ("version: "+VERSION);
out.println ();
if (message!=null) out.println (message);
}
}
diff --git a/src/com/iovar/web/bin/shell/task/Block.java b/src/com/iovar/web/bin/shell/task/Block.java
--- a/src/com/iovar/web/bin/shell/task/Block.java
+++ b/src/com/iovar/web/bin/shell/task/Block.java
@@ -1,57 +1,57 @@
/*
- * Copyright (C) 2013-2014 Lannocc Technologies
+ * Copyright (C) 2013-2015 Lannocc Technologies
* @%@~LICENSE~@%@
*/
package com.iovar.web.bin.shell.task;
// local imports:
import com.iovar.web.dev.*;
import com.iovar.web.proc.*;
// java imports:
import java.io.*;
import javax.servlet.*;
// 3rd-party imports:
/**
* A formalized group (i.e. list of operations surrounded by curly braces, invoked sequentially in a sub-shell).
*
* @author shawn@lannocc.com
*/
class Block implements Graph, Task
{
Group ops;
public boolean assemble (final GraphReader in) throws GraphException, IOException
{
if ('{' != in.peek ()) return false;
// FIXME: we really need a double-peek so we can require whitespace or newline after opening brace
in.discard ();
Log.debug ("will assemble");
ops = new Group ();
if (!ops.assemble (in)) ops = null;
if ('}' != in.peek ()) throw new GraphException ("Missing closing curly brace");
in.discard ();
return true;
}
- public Return exec (final ShellSession.Instance shell, final ServletContext context, final String user, final InputStream in, final String contentType) throws IOException, ServletException
+ public Return exec (final TaskData task) throws IOException, ServletException
{
- return ops.exec (shell, context, user, in, contentType);
+ return ops.exec (task);
}
public String toString ()
{
final StringBuffer s = new StringBuffer ();
s.append ("{ ");
if (ops!=null) s.append (ops.toString ());
s.append (" }");
return s.toString ();
}
}
diff --git a/src/com/iovar/web/bin/shell/task/Call.java b/src/com/iovar/web/bin/shell/task/Call.java
--- a/src/com/iovar/web/bin/shell/task/Call.java
+++ b/src/com/iovar/web/bin/shell/task/Call.java
@@ -1,384 +1,382 @@
/*
- * Copyright (C) 2013-2014 Lannocc Technologies
+ * Copyright (C) 2013-2015 Lannocc Technologies
* @%@~LICENSE~@%@
*/
package com.iovar.web.bin.shell.task;
// local imports:
import com.iovar.web.*;
import com.iovar.web.bin.shell.*;
import com.iovar.web.dev.*;
import com.iovar.web.dev.trans.Http;
import com.iovar.web.proc.*;
// java imports:
import java.io.*;
import java.util.*;
import javax.servlet.*;
// 3rd-party imports:
/**
* Invoke a named executable with optional parameters/arguments.
*
* @author shawn@lannocc.com
*/
public class Call extends Command implements Graph, Task
{
List<CommandText> data;
List<Parameter> params;
// normalized
String cmd;
List<String> args;
Call ()
{
/*
this.args = new ArrayList<CommandText> ();
*/
this.data = new ArrayList<CommandText> ();
this.params = new ArrayList<Parameter> ();
this.cmd = null;
this.args = null;
}
public boolean assemble (final GraphReader in) throws IOException, GraphException
{
// re-usable objects for testing
CommandText text = new CommandText ();
Whitespace white = new Whitespace ();
Parameter param = new Parameter ();
if (! text.assemble (in)) return false;
Log.debug ("will assemble");
// System.out.println ("call name: "+this.name);
this.data.add (text);
text = new CommandText ();
for (int c; (c = in.peek ())>=0; )
{
if (white.assemble (in))
{
//white = new Whitespace ();
}
if (param.assemble (in))
{
this.params.add (param);
param = new Parameter ();
}
else if (text.assemble (in))
{
this.data.add (text);
text = new CommandText ();
}
else
{
return true;
}
}
return true;
}
/*
* FIXME: this should be improved (allow named parameters, too?)
*/
void normalize (ShellSession.Instance shell, final ServletContext context)
{
StringBuffer cmd = null;
final List<String> args = new ArrayList<String> ();
boolean inArgs = false;
StringBuffer arg = null;
for (final CommandText segment : data)
{
for (final Text text : segment.items)
{
final String str = text.value (shell, context);
if (str==null)
{
continue;
}
if (text instanceof Variable)
{
char c;
for (int i=0; i<str.length (); i++)
{
c = str.charAt (i);
if (' '==c || '\t'==c)
{
if (cmd==null)
{
continue;
}
inArgs = true;
if (arg!=null)
{
args.add (arg.toString ());
arg = null;
}
}
else
{
if (! inArgs)
{
if (cmd==null)
{
cmd = new StringBuffer ();
}
cmd.append (c);
}
else
{
if (arg==null)
{
arg = new StringBuffer ();
}
arg.append (c);
}
}
}
}
else
{
if (! inArgs)
{
if (cmd==null)
{
cmd = new StringBuffer ();
}
cmd.append (str);
}
else
{
if (arg==null)
{
arg = new StringBuffer ();
}
arg.append (str);
}
}
}
if (! inArgs)
{
inArgs = true;
}
else if (arg!=null)
{
args.add (arg.toString ());
arg = null;
}
}
if (arg!=null)
{
args.add (arg.toString ());
arg = null;
}
this.cmd = cmd.toString ();
this.args = args;
}
- public Return exec (ShellSession.Instance shell, final ServletContext context, final String user, final InputStream in, final String contentType) throws IOException, ServletException
+ public Return exec (final TaskData task) throws IOException, ServletException
{
Log.info ("executing: "+this);
- Log.debug ("session: "+shell);
-// Log.debug ("vars: "+vars);
- Log.debug ("environment: "+shell.getEnvironment ());
+ Log.debug ("task: "+task);
// 0. normalize command name / arguments
if (this.cmd==null)
{
- normalize (shell, context);
+ normalize (task.shell, task.context);
}
// 1. run shell processing on named parameters
final Map<String,List<String>> params = new HashMap<String,List<String>> ();
for (final Parameter param : this.params)
{
- final String name = param.getName ().value (shell, context);
+ final String name = param.getName ().value (task.shell, task.context);
final CommandText value = param.getValue ();
List<String> vals = params.get (name);
if (vals==null)
{
vals = new ArrayList<String> ();
params.put (name, vals);
}
- vals.add (value!=null ? value.value (shell, context) : null );
+ vals.add (value!=null ? value.value (task.shell, task.context) : null );
}
// 2. add anonymous arguments to parameter map
if (this.args!=null && ! this.args.isEmpty ())
{
params.put (null, args);
}
// 3. clear previous exit value
- shell.clearExit ();
- shell.save (context);
+ task.shell.clearExit ();
+ task.shell.save (task.context);
// 4. fork the session
- final ShellSession.Instance local = shell.fork (context);
+ final ShellSession.Instance local = task.shell.fork (task.context);
Log.debug ("forked local session: "+local);
// 5. any named parameters will be passed in as variables.
for (final Map.Entry<String,List<String>> param : params.entrySet ())
{
final String name = param.getKey ();
final List<String> vals = param.getValue ();
final StringBuffer sval = new StringBuffer ();
String sep = "";
if (vals!=null) for (final String val : vals)
{
sval.append (sep).append (val);
sep = ",";
}
local.set (name, sval.toString ());
}
// 6. set locals
final StringBuffer all = new StringBuffer ();
String sep = "";
int index=0;
if (args!=null)
{
for (final String arg : args)
{
all.append (sep).append (arg);
sep = " ";
local.setLocal (String.valueOf (++index), arg);
}
}
local.setLocal ("#", String.valueOf (index));
local.setLocal ("@", all.toString ());
// 7. set user
- local.set (ShellSession.ENV_USER, user);
+ local.set (ShellSession.ENV_USER, task.user);
// 8. invoke
// FIXME: hardcoded path to shell session storage
params.put (Shell.EXT_PARAM_SESSION, Arrays.asList (new String[] { "local:/proc/shell/"+local.getId () }));
- final Transport t = Which.reference (shell, context, user, cmd.toString (), params);
+ final Transport t = Which.reference (task.shell, task.context, task.user, cmd.toString (), params);
if (t!=null)
{
- return invoke (local, t, context, in, contentType, params);
+ return invoke (local, t, task.context, task.in, task.contentType, params);
}
else
{
Log.error ("executable resource not found (even after searching through $PATH, if relative): "+cmd);
return Utils.error ("executable resource not found (even after searching through $PATH, if relative): "+cmd);
}
}
static Return invoke (final ShellSession.Instance shell, final Transport t, final ServletContext context, final InputStream in, final String contentType, final Map<String,List<String>> params) throws IOException
{
shell.setLocal ("0", t.toPathString ());
// FIXME: hardcoded path to shell session storage
//shell.saveTo ("local:/proc/shell/"+shell.getId (), context);
shell.save (context);
Log.debug ("invoking: "+t.toPathString ());
final Return r = t.post (in, contentType);
Log.debug ("invoking returned: "+r);
if (r.status instanceof Http.Status && ((Http.Status) r.status).isRedirect ())
{
Log.debug ("return has redirect status: "+r);
final String target = ((Http.Status) r.status).getHeaders ().get ("Location").get (0);
Log.info ("redirecting to target: "+target);
return invoke (shell, Transport.handler (target, params, context), context, in, contentType, params);
}
else
{
return r;
}
}
public String toString ()
{
if (this.cmd==null)
{
final StringBuffer s = new StringBuffer ("<(non-normalized call)");
for (final CommandText text : this.data)
{
s.append (" ").append (text);
}
s.append (">");
return s.toString ();
}
else
{
final StringBuffer s = new StringBuffer ("<"+this.cmd+">");
for (final Parameter param : params)
{
s.append (" "+param);
}
if (this.args!=null)
{
for (final String arg : this.args)
{
s.append (" ["+arg+"]");
}
}
return s.toString ();
}
}
public static Call valueOf (final String name, final Map<String,List<String>> params)
{
final Call call = new Call ();
call.cmd = name;
if (params!=null) for (final Map.Entry<String,List<String>> entry : params.entrySet ())
{
final String key = entry.getKey ();
if (key==null)
{
call.args = new ArrayList<String> ();
for (final String val : entry.getValue ())
{
call.args.add (val);
}
}
else if (entry.getValue ()==null)
{
call.params.add (new Parameter (key, null));
}
else
{
for (final String val : entry.getValue ())
{
call.params.add (new Parameter (key, val));
}
}
}
return call;
}
}
diff --git a/src/com/iovar/web/bin/shell/task/Group.java b/src/com/iovar/web/bin/shell/task/Group.java
--- a/src/com/iovar/web/bin/shell/task/Group.java
+++ b/src/com/iovar/web/bin/shell/task/Group.java
@@ -1,238 +1,241 @@
/*
- * Copyright (C) 2013-2014 Lannocc Technologies
+ * Copyright (C) 2013-2015 Lannocc Technologies
* @%@~LICENSE~@%@
*/
package com.iovar.web.bin.shell.task;
// local imports:
import com.iovar.web.*;
import com.iovar.web.dev.*;
import com.iovar.web.proc.*;
// java imports:
import java.io.*;
import java.util.*;
import javax.servlet.*;
// 3rd-party imports:
/**
* One or more comments (ignored), blocks and/or operations, invoked sequentially (an informal list), sometimes conditionally.
* Will not assemble an empty group (however a group may simply be whitespace or a comment). [IS THIS STILL TRUE???--SAW]
*
* Presently all output is combined.
*
* @author shawn@lannocc.com
*/
public class Group implements Graph, Task
{
List tasks; // list of Task items as well as possible conditionials (Or, And)
public Group ()
{
tasks = new ArrayList (20);
}
public boolean assemble (final GraphReader in) throws IOException, GraphException
{
// re-usable objects for testing
Whitespace white = new Whitespace ();
Comment blah = new Comment ();
Block block = new Block ();
Operation op = new Operation ();
Or or = new Or ();
And and = new And ();
if (in.peek ()<0) return false;
Log.debug ("will assemble");
while (true)
{
if (white.assemble (in))
{
//white = new Whitespace ();
}
else if (blah.assemble (in))
{
//blah = new Comment ();
}
else if (block.assemble (in))
{
tasks.add (block);
block = new Block ();
}
else if (';'==in.peek () || '\n'==in.peek ())
{
in.discard ();
}
else if (op.assemble (in))
{
tasks.add (op);
op = new Operation ();
}
else if (or.assemble (in))
{
if (tasks.isEmpty () || ! (tasks.get (tasks.size ()-1) instanceof Task))
{
throw new GraphException ("expecting a Task to precede 'Or' condition: "+or);
}
tasks.add (or);
or = new Or ();
}
else if (and.assemble (in))
{
if (tasks.isEmpty () || ! (tasks.get (tasks.size ()-1) instanceof Task))
{
throw new GraphException ("expecting a Task to precede 'And' condition: "+and);
}
tasks.add (and);
and = new And ();
}
else if (in.peek ()<0)
{
if (! tasks.isEmpty ())
{
final Object last = tasks.get (tasks.size ()-1);
if (! (last instanceof Task))
{
throw new GraphException ("expecting a Task to follow the last condition: "+last);
}
}
// FIXME: don't we actually want: return tasks.size()>1; ???
return true;
}
else if ('}'==in.peek ())
{
Log.debug ("found closing curly brace and returning");
// FIXME: don't we actually want: return tasks.size()>1; ???
return true;
}
else
{
throw new GraphException ("unexpected (Group): "+((char) in.peek ()));
}
}
}
- public Return exec (ShellSession.Instance shell, final ServletContext context, final String user, InputStream in, final String contentType) throws IOException, ServletException
+ public Return exec (final TaskData tdata) throws IOException, ServletException
{
Log.debug ("exec: "+this);
final ByteArrayOutputStream out = new ByteArrayOutputStream ();
Return r = null;
Task task = null;
+ ShellSession.Instance shell = tdata.shell;
+ InputStream in = tdata.in;
+
boolean skip = false;
for (final Object taskOrCondition : tasks)
{
if (skip) // ignore current item
{
skip = false;
}
else if (taskOrCondition instanceof Task)
{
task = (Task) taskOrCondition;
final Return r_old = r;
Log.debug ("task exec: "+task);
- r = task.exec (shell, context, user, in, contentType);
+ r = task.exec (new TaskData (shell, tdata.context, tdata.user, in, tdata.contentType));
Log.debug ("task exec returned: "+r);
if (r==null) r = r_old; // may be null if operation was simply variable assignment
if (r!=null) Utils.pipe (r.data, out);
// session may have changed after exec
Log.debug ("reloading session: "+shell);
// FIXME: hardcoded path to shell session storage
- shell = (ShellSession.Instance) Utils.xstream.fromXML (Transport.handler ("local:/proc/shell/"+shell.getId (), context).get ());
+ shell = (ShellSession.Instance) Utils.xstream.fromXML (Transport.handler ("local:/proc/shell/"+shell.getId (), tdata.context).get ());
/*
final ObjectInputStream sin = new ObjectInputStream (Transport.handler ("local:/proc/shell/"+shell.getId (), context).get ()); try
{
shell = (ShellSession.Instance) sin.readObject ();
}
catch (final ClassNotFoundException e)
{
Log.error ("while reloading session: "+e);
throw new IOException (e);
}
finally
{
sin.close ();
}
*/
in = null;
}
else if (taskOrCondition instanceof Or || taskOrCondition instanceof And)
{
Log.debug (" r.status.affirmative(): "+r.status.affirmative ());
Log.debug (" shell.getExit(): "+shell.getExit ());
skip = r.status.affirmative () && (shell.getExit ()==null || shell.getExit ()==0)
? taskOrCondition instanceof Or
: taskOrCondition instanceof And;
}
else
{
throw new RuntimeException ("unsupported task or condition type: "+taskOrCondition);
}
}
if (r!=null)
{
return new Return (r.type, r.disposition, new ByteArrayInputStream (out.toByteArray()), r.status);
}
else // r may be null for a single operation that is simply a variable assignment
{
return null;
}
}
public String toString ()
{
final StringBuffer s = new StringBuffer ();
String sep = "";
for (final Object taskOrCondition : tasks)
{
s.append (sep).append (taskOrCondition);
sep = "\n";
}
return s.toString ();
}
public static void main (final String [] args) throws Exception
{
// final GraphReader in = new GraphReader (new StringReader ("\n\t\t \n ab;{'c' foo; } bar$blah \"ok then\"$something and${more}stuff and\"not\"'$more' ; #ok\n x y z"));
final GraphReader in = new GraphReader (new StringReader (
"echo hello>yo | echo world"
/*
"local:/bin/export PATH=local:/usr/local/bin,local:/usr/bin,local:/bin,local:/opt/bin\n"+
"\n"+
"# USER is set by the initial shell\n"+
"#export HOME=local:/home/${USER}\n"+
""
*/
));
Group g = new Group ();
try
{
g.assemble (in);
System.out.println (g);
}
catch (final GraphException e)
{
System.err.println ("bad syntax: "+e.getMessage ());
//System.err.println (" @ "+in.getLine ()+", "+in.getColumn ());
e.printStackTrace();
}
}
}
diff --git a/src/com/iovar/web/bin/shell/task/HotQuoted.java b/src/com/iovar/web/bin/shell/task/HotQuoted.java
--- a/src/com/iovar/web/bin/shell/task/HotQuoted.java
+++ b/src/com/iovar/web/bin/shell/task/HotQuoted.java
@@ -1,165 +1,165 @@
/*
- * Copyright (C) 2014 Lannocc Technologies
+ * Copyright (C) 2014-2015 Lannocc Technologies
* @%@~LICENSE~@%@
*/
package com.iovar.web.bin.shell.task;
// local imports:
import com.iovar.web.dev.*;
import com.iovar.web.proc.*;
// java imports:
import java.io.*;
import java.util.*;
import javax.servlet.*;
// 3rd-party imports:
/**
*
* @author shawn@lannocc.com
*/
class HotQuoted implements Graph, Text, Task
{
List<Graph> items;
HotQuoted ()
{
this.items = new ArrayList<Graph> (30);
}
public boolean assemble (final GraphReader in) throws IOException, GraphException
{
if ('`'!=in.peek ()) return false;
in.discard ();
// re-usable objects for testing
Escaped escaped = new Escaped ();
Variable variable = new Variable ();
Literal literal = new Literal ();
for (int c; (c = in.peek ()) >= 0; )
{
if ('`'==c)
{
in.discard ();
return true;
}
if (escaped.assemble (in))
{
items.add (escaped);
escaped = new Escaped ();
}
else if (variable.assemble (in))
{
items.add (variable);
variable = new Variable ();
}
else if (literal.assemble (in))
{
items.add (literal);
literal = new Literal ();
}
else
{
throw new GraphException ();
}
}
throw new GraphException ("Missing closing back-quote: "+string ());
}
public String toString ()
{
return '`'+string ()+'`';
}
String string ()
{
final StringBuffer s = new StringBuffer ();
for (final Graph item : items)
{
s.append (item.toString ());
}
return s.toString ();
}
public String value (final ShellSession.Instance shell, final ServletContext context)
{
try
{
/* FIXME (user) */
- final Return r = exec (shell, context, null, null, null);
+ final Return r = exec (new TaskData (shell, context, null));
if (r==null)
{
Log.error ("null return for "+this);
return null;
}
if (r.type==null || !r.type.startsWith ("text/"))
{
Log.warn ("non-text mime type returned for "+this+": "+r.type);
}
if (r.data==null)
{
Log.warn ("null data stream returned for "+this);
return null;
}
final StringBuffer s = new StringBuffer ();
final InputStreamReader in = new InputStreamReader (r.data);
for (int c; (c = in.read ())>=0; s.append ((char) c));
return s.toString ();
}
catch (final IOException e)
{
Log.error ("shell exec failed", e);
return "shell exec failed: "+e;
}
catch (final ServletException e)
{
Log.error ("shell exec failed", e);
return "shell exec failed: "+e;
}
}
- public Return exec (ShellSession.Instance shell, final ServletContext context, final String user, InputStream in, final String contentType) throws IOException, ServletException
+ public Return exec (final TaskData task) throws IOException, ServletException
{
Log.debug ("exec: "+this);
final StringBuffer value = new StringBuffer ();
for (final Graph item : items)
{
if (item instanceof Text)
{
- value.append (((Text) item).value (shell, context));
+ value.append (((Text) item).value (task.shell, task.context));
}
else
{
value.append (item.toString ());
}
}
try
{
final Group group = new Group ();
if (! group.assemble (new GraphReader (new StringReader (value.toString ()))))
{
Log.warn ("empty assembly for "+this);
return null; //FIXME?
}
- return group.exec (shell, context, user, in, contentType);
+ return group.exec (task);
}
catch (final GraphException e)
{
Log.error ("graph assembly failed for "+this, e);
throw new ServletException ("graph assembly failed", e);
}
}
}
diff --git a/src/com/iovar/web/bin/shell/task/Operation.java b/src/com/iovar/web/bin/shell/task/Operation.java
--- a/src/com/iovar/web/bin/shell/task/Operation.java
+++ b/src/com/iovar/web/bin/shell/task/Operation.java
@@ -1,269 +1,273 @@
/*
- * Copyright (C) 2013-2014 Lannocc Technologies
+ * Copyright (C) 2013-2015 Lannocc Technologies
* @%@~LICENSE~@%@
*/
package com.iovar.web.bin.shell.task;
// local imports:
import com.iovar.web.*;
import com.iovar.web.dev.*;
import com.iovar.web.proc.*;
// java imports:
import java.io.*;
import java.util.*;
import javax.servlet.*;
// 3rd-party imports:
/**
* A set of commands invoked together as a single unit.
*
* @author shawn@lannocc.com
*/
class Operation implements Graph, Task
{
List<Command> cmds;
public Operation ()
{
cmds = new ArrayList<Command> (10);
}
/**
* Note that this may gobble up whitespace yet fail assembly.
*/
public boolean assemble (final GraphReader in) throws GraphException, IOException
{
Log.debug ("attempting assembly");
// re-usable objects for testing
VariableAssign var = new VariableAssign ();
Call call = new Call ();
Input input = new Input ();
Output output = new Output ();
Chain chain = new Chain ();
Background back = new Background ();
Whitespace white = new Whitespace ();
boolean setVar = false; // have any variable assignments?
Object lastCallOrChain = null;
if (in.peek ()<0) return false;
// if there are any variable assignments they have to come first
while (var.assemble (in))
{
setVar = true;
cmds.add (var);
var = new VariableAssign ();
if (white.assemble (in))
{
//white = new Whitespace ();
}
}
while (true)
{
if (call.assemble (in))
{
if (lastCallOrChain!=null && lastCallOrChain instanceof Call)
{
throw new GraphException ("not expecting another call here: "+call);
}
cmds.add (call);
lastCallOrChain = call;
call = new Call ();
}
else if (input.assemble (in))
{
cmds.add (input);
input = new Input ();
}
else if (output.assemble (in))
{
cmds.add (output);
output = new Output ();
}
else if (chain.assemble (in))
{
Log.debug ("lastCallOrChain: "+lastCallOrChain);
if (lastCallOrChain==null || ! (lastCallOrChain instanceof Call))
{
throw new GraphException ("expecting a call to come somewhere before chain command: "+chain);
}
cmds.add (chain);
lastCallOrChain = chain;
chain = new Chain ();
}
else if (back.assemble (in))
{
if (lastCallOrChain==null)
{
throw new GraphException ("expecting a call or chain to come before background command: "+back);
}
cmds.add (back);
lastCallOrChain = back;
back = new Background ();
}
else if (white.assemble (in))
{
//white = new Whitespace ();
}
else if (cmds.size ()<1)
{
return false;
}
else if (lastCallOrChain==null)
{
if (setVar) return true;
throw new GraphException ("operation expects a call (or variable assignment, at least) to come somewhere: "+(char) in.peek ());
}
else if (! (lastCallOrChain instanceof Call || lastCallOrChain instanceof Background))
{
throw new GraphException ("operation needs to end with a call");
}
else
{
return true;
}
}
}
/**
* Note this returns <tt>null</tt> if the operation is merely a variable assignment.
*/
- public Return exec (ShellSession.Instance shell, final ServletContext context, final String user, final InputStream in, final String contentType) throws IOException, ServletException
+ public Return exec (final TaskData task) throws IOException, ServletException
{
- Log.debug ("passed-in contentType: "+contentType);
+ Log.debug ("passed-in contentType: "+task.contentType);
// FIXME: use PipedInputStream + PipedOutputStream in a new thread
final ByteArrayOutputStream out = new ByteArrayOutputStream ();
Return r = null;
+ ShellSession.Instance shell = task.shell;
+
Call call = null;
Input from = null;
Output to = null;
for (final Command cmd : cmds)
{
if (cmd instanceof VariableAssign)
{
final VariableAssign var = (VariableAssign) cmd;
final String name = var.getName ();
final CommandText val = var.getValue ();
// FIXME: re-evaluate this...
- shell.set (name, val!=null ? val.value (shell, context) : null);
+ shell.set (name, val!=null ? val.value (shell, task.context) : null);
//shell.export (1, context, name, val!=null ? val.value (shell, context) : null);
// FIXME: not sure about saving here
- shell.save (context);
+ shell.save (task.context);
//shell.saveUp (1, context);
}
else if (cmd instanceof Call)
{
call = (Call) cmd;
}
else if (cmd instanceof Chain)
{
- r = call.exec (shell, context, user, from!=null ? from.read (shell, context) : (r!=null ? r.data : in),
- from!=null ? null : (r!=null ? r.type : contentType));
+ r = call.exec (new TaskData (shell, task.context, task.user, from!=null ? from.read (shell, task.context) : (r!=null ? r.data : task.in),
+ from!=null ? null : (r!=null ? r.type : task.contentType)));
Utils.pipe (r.data, out);
r = new Return (r.type, r.disposition, new ByteArrayInputStream (out.toByteArray ()), r.status);
out.reset();
// session may have changed after exec
Log.debug ("reloading session: "+shell);
// FIXME: hardcoded path to shell session storage
- shell = (ShellSession.Instance) Utils.xstream.fromXML (Transport.handler ("local:/proc/shell/"+shell.getId (), context).get ());
+ shell = (ShellSession.Instance) Utils.xstream.fromXML (Transport.handler ("local:/proc/shell/"+shell.getId (), task.context).get ());
/*
final ObjectInputStream sin = new ObjectInputStream (Transport.handler ("local:/proc/shell/"+shell.getId (), context).get ()); try
{
shell = (ShellSession.Instance) sin.readObject ();
}
catch (final ClassNotFoundException e)
{
Log.error ("while reloading session: "+e);
throw new IOException (e);
}
finally
{
sin.close ();
}
*/
call = null;
}
else if (cmd instanceof Input)
{
from = (Input) cmd;
}
else if (cmd instanceof Output)
{
to = (Output) cmd;
}
else if (cmd instanceof Background)
{
Log.debug ("background: "+cmd);
- final Job job = Job.background (call, shell, context, user, from!=null ? from.read (shell, context) : (r!=null ? r.data : in));
+
+ // FIXME: content-type not yet utilized here
+ final Job job = Job.background (call, new TaskData (shell, task.context, task.user, from!=null ? from.read (shell, task.context) : (r!=null ? r.data : task.in), ""));
out.write (job.toString ().getBytes ());
r = new Return (new ByteArrayInputStream (out.toByteArray ()), new Status () {
public boolean affirmative () { return true; }
});
out.reset ();
call = null;
}
else
{
throw new ServletException ("unsupported Command type: "+cmd.getClass ());
}
}
if (call!=null)
{
if (to!=null)
{
// FIXME: contentType not fully utilized here
- r = call.exec (shell, context, user, from!=null ? from.read (shell, context) : (r!=null ? r.data : in),
- from!=null ? null : (r!=null ? r.type : contentType));
+ r = call.exec (new TaskData (shell, task.context, task.user, from!=null ? from.read (shell, task.context) : (r!=null ? r.data : task.in),
+ from!=null ? null : (r!=null ? r.type : task.contentType)));
Utils.pipe (r.data, out);
- return new Return (to.write (shell, context, new ByteArrayInputStream (out.toByteArray ()), r.type), r.status);
+ return new Return (to.write (shell, task.context, new ByteArrayInputStream (out.toByteArray ()), r.type), r.status);
}
else
{
// FIXME: contentType not fully utilized here???
- return call.exec (shell, context, user, from!=null ? from.read (shell, context) : (r!=null ? r.data : in),
- from!=null ? null : (r!=null ? r.type : contentType));
+ return call.exec (new TaskData (shell, task.context, task.user, from!=null ? from.read (shell, task.context) : (r!=null ? r.data : task.in),
+ from!=null ? null : (r!=null ? r.type : task.contentType)));
}
}
else
{
return r;
}
}
public String toString ()
{
final StringBuffer s = new StringBuffer ();
String sep = "";
for (final Command cmd : cmds)
{
s.append (sep).append (cmd);
sep = " ";
}
return s.toString ();
}
}
diff --git a/src/com/iovar/web/bin/shell/task/Return.java b/src/com/iovar/web/bin/shell/task/Return.java
--- a/src/com/iovar/web/bin/shell/task/Return.java
+++ b/src/com/iovar/web/bin/shell/task/Return.java
@@ -1,46 +1,46 @@
/*
* Copyright (C) 2014 Lannocc Technologies
* @%@~LICENSE~@%@
*/
package com.iovar.web.bin.shell.task;
// local imports:
import com.iovar.web.dev.Log;
// java imports:
import java.io.*;
// 3rd-party imports:
/**
- * Container for things that a task returns (presently mime-type, InputStream, Status).
+ * Container for things that a task returns (presently mime-type, disposition, InputStream, Status).
*
* @author shawn@lannocc.com
*/
public class Return
{
public final String type;
public final String disposition;
public final InputStream data;
public final Status status;
public Return (final InputStream data, final Status status)
{
this (null, null, data, status);
}
public Return (final String type, final String disposition, final InputStream data, final Status status)
{
this.type = type;
this.disposition = disposition;
this.data = data;
this.status = status;
Log.debug ("new Return ( "+type+", "+disposition+", "+data+", "+status+" )");
}
public String toString ()
{
return super.toString () + " [type="+type+", disposition="+disposition+", data="+data+", status="+status+"]";
}
}
diff --git a/src/com/iovar/web/bin/shell/task/Script.java b/src/com/iovar/web/bin/shell/task/Script.java
--- a/src/com/iovar/web/bin/shell/task/Script.java
+++ b/src/com/iovar/web/bin/shell/task/Script.java
@@ -1,67 +1,67 @@
/*
- * Copyright (C) 2013-2014 Lannocc Technologies
+ * Copyright (C) 2013-2015 Lannocc Technologies
* @%@~LICENSE~@%@
*/
package com.iovar.web.bin.shell.task;
// local imports:
import com.iovar.web.dev.*;
import com.iovar.web.proc.*;
// java imports:
import java.io.*;
import javax.servlet.*;
// 3rd-party imports:
/**
* An optional named interpreter followed by group of operations.
* Empty input will not assemble.
*
* @author shawn@lannocc.com
*/
public class Script implements Graph, Task
{
HashBang interpreter;
Group ops;
public boolean assemble (final GraphReader in) throws IOException, GraphException
{
interpreter = new HashBang ();
if (! interpreter.assemble (in))
{
interpreter = null;
}
Log.debug ("hashbang: "+interpreter);
if (interpreter != null) Log.info ("interpreter: "+interpreter.getVal());
ops = new Group ();
if (! ops.assemble (in))
{
Log.debug ("no group assembly");
return false;
}
return true;
}
/*
public HashBang getInterpreter ()
{
return interpreter;
}
*/
/*
* this is shell exec only: the calling party is responsible for invoking any other interpreter
*/
- public Return exec (final ShellSession.Instance shell, final ServletContext context, final String user, final InputStream in, final String contentType) throws IOException, ServletException
+ public Return exec (final TaskData task) throws IOException, ServletException
{
- return ops.exec (shell, context, user, in, contentType);
+ return ops.exec (task);
}
public String toString ()
{
return super.toString ()+": interpreter="+interpreter+", { "+ops+" }";
}
}
diff --git a/src/com/iovar/web/bin/shell/task/Task.java b/src/com/iovar/web/bin/shell/task/Task.java
--- a/src/com/iovar/web/bin/shell/task/Task.java
+++ b/src/com/iovar/web/bin/shell/task/Task.java
@@ -1,26 +1,26 @@
/*
- * Copyright (C) 2013-2014 Lannocc Technologies
+ * Copyright (C) 2013-2015 Lannocc Technologies
* @%@~LICENSE~@%@
*/
package com.iovar.web.bin.shell.task;
// local imports:
import com.iovar.web.proc.*;
// java imports:
import java.io.*;
import javax.servlet.*;
// 3rd-party imports:
/**
* Any number of executable things.
*
* @author shawn@lannocc.com
*/
interface Task
{
//Status run (final Reader in, final Writer out, final Writer err) throws IOException;
- Return exec (ShellSession.Instance shell, final ServletContext context, final String user, final InputStream in, final String contentType) throws IOException, ServletException;
+ Return exec (final TaskData task) throws IOException, ServletException;
}
diff --git a/src/com/iovar/web/bin/shell/task/TaskData.java b/src/com/iovar/web/bin/shell/task/TaskData.java
new file mode 100644
--- /dev/null
+++ b/src/com/iovar/web/bin/shell/task/TaskData.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2015 Lannocc Technologies
+ * @%@~LICENSE~@%@
+ */
+package com.iovar.web.bin.shell.task;
+
+// local imports:
+import com.iovar.web.proc.*;
+
+// java imports:
+import java.io.*;
+import javax.servlet.*;
+
+// 3rd-party imports:
+
+/**
+ * Encapsulation of all the data required for task execution.
+ *
+ * @author shawn@lannocc.com
+ */
+public class TaskData
+{
+ public final ShellSession.Instance shell;
+ public final ServletContext context;
+ public final String user;
+ public final InputStream in;
+ public final String contentType;
+
+ /**
+ * Minimal constructor.
+ */
+ public TaskData (final ShellSession.Instance shell, final ServletContext context, final String user)
+ {
+ this (shell, context, user, null, null);
+ }
+
+ /**
+ * Maximal constructor.
+ */
+ public TaskData (final ShellSession.Instance shell, final ServletContext context, final String user, final InputStream in, final String contentType)
+ {
+ this.shell = shell;
+ this.context = context;
+ this.user = user;
+ this.in = in;
+ this.contentType = contentType;
+ }
+
+ public String toString ()
+ {
+ return super.toString () + " { shell: "+shell+" }";
+ }
+}
diff --git a/src/com/iovar/web/proc/Job.java b/src/com/iovar/web/proc/Job.java
--- a/src/com/iovar/web/proc/Job.java
+++ b/src/com/iovar/web/proc/Job.java
@@ -1,127 +1,120 @@
/*
- * Copyright (C) 2014 Lannocc Technologies
+ * Copyright (C) 2014-2015 Lannocc Technologies
* @%@~LICENSE~@%@
*/
package com.iovar.web.proc;
// local imports:
import com.iovar.web.*;
import com.iovar.web.bin.shell.task.*;
import com.iovar.web.dev.*;
import com.iovar.web.bin.shell.task.Return;
// java imports:
import java.io.*;
import java.util.*;
import javax.servlet.*;
// 3rd-party imports:
/**
* Support for jobs (currently that means backgrounded tasks).
*
* @author shawn@lannocc.com
*/
public class Job implements Runnable
{
Thread thread;
final Call call;
- final ShellSession.Instance shell;
- final ServletContext context;
- final String user;
- final InputStream in;
+ final TaskData task;
Return r;
ByteArrayOutputStream out;
- Job (final Call call, final ShellSession.Instance shell, final ServletContext context, final String user, final InputStream in)
+ Job (final Call call, final TaskData task)
{
this.call = call;
- this.shell = shell;
- this.context = context;
- this.user = user;
- this.in = in;
+ this.task = task;
this.out = new ByteArrayOutputStream ();
}
public void run ()
{
try
{
- // FIXME: contentType not yet utilized here
- r = call.exec (shell, context, user, in, "");
+ r = call.exec (task);
Utils.pipe (r.data, out);
}
catch (final Exception e)
{
try
{
out.write (("job: while buffering background output: "+e).getBytes ());
}
catch (final IOException ee)
{
Log.error ("ack! while handling: "+e, ee);
throw new RuntimeException (ee);
}
}
}
public Return join (final ServletContext context) throws InterruptedException
{
thread.join();
thread = null;
final Map<Integer,Job> jobs = getJobs (context);
jobs.remove (getId ());
// FIXME: session may have changed after exec
final ByteArrayInputStream in = new ByteArrayInputStream (out.toByteArray ());
return new Return (in, this.r.status);
}
public String toString ()
{
// FIXME: track a local (easier) job number for the brackets
return "["+getId ()+"] "+getId ();
}
int getId ()
{
return System.identityHashCode (this);
}
- public static Job background (final Call call, final ShellSession.Instance shell, final ServletContext context, final String user, final InputStream in)
+ public static Job background (final Call call, final TaskData task)
{
- Map<Integer,Job> jobs = getJobs (context);
+ Map<Integer,Job> jobs = getJobs (task.context);
if (jobs==null)
{
jobs = new HashMap<Integer, Job> ();
- context.setAttribute (Job.class.getName (), jobs);
+ task.context.setAttribute (Job.class.getName (), jobs);
}
- final Job job = new Job (call, shell, context, user, in);
+ final Job job = new Job (call, task);
Log.info ("job backgrounded: "+job);
jobs.put (job.getId (), job);
job.thread = new Thread (job);
job.thread.start ();
Log.debug (" background thread started");
return job;
}
public static Map<Integer,Job> getJobs (final ServletContext context)
{
return (Map<Integer,Job>) context.getAttribute (Job.class.getName ());
}
public static Job get (final Integer id, final ServletContext context)
{
final Map<Integer,Job> jobs = getJobs (context);
if (jobs==null) return null;
return jobs.get (id);
}
}
diff --git a/src/com/iovar/web/sbin/Init.java b/src/com/iovar/web/sbin/Init.java
--- a/src/com/iovar/web/sbin/Init.java
+++ b/src/com/iovar/web/sbin/Init.java
@@ -1,151 +1,153 @@
/*
- * Copyright (C) 2014 Lannocc Technologies
+ * Copyright (C) 2014-2015 Lannocc Technologies
* @%@~LICENSE~@%@
*/
package com.iovar.web.sbin;
// local imports:
import com.iovar.web.*;
import com.iovar.web.bin.shell.*;
+import com.iovar.web.bin.shell.task.TaskData;
import com.iovar.web.dev.*;
import com.iovar.web.proc.*;
// java imports:
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.xml.parsers.*;
import org.w3c.dom.*;
import org.xml.sax.SAXException;
// 3rd-party imports:
/**
* System initialization.
* Servlet init() looks for 'inittab' init-param that points to location of
* init configuration file describing the actual steps.
*
* @author shawn@lannocc.com
*/
public class Init extends HttpServlet
{
public static final String INIT_PARAM = "inittab";
public static final String TAG_INIT = "init";
public static final String TAG_BOOT_WAIT = "bootwait";
protected void doHead (final HttpServletRequest req, final HttpServletResponse resp) throws IOException
{
Utils.doHead (this, req, resp);
}
protected void doGet (final HttpServletRequest req, final HttpServletResponse resp) throws IOException
{
Utils.doGet (this, req, resp);
}
protected void doPut (final HttpServletRequest req, final HttpServletResponse resp) throws IOException
{
Utils.doPut (this, req, resp);
}
public void init ()
{
final String init = this.getInitParameter (INIT_PARAM);
if (init==null) return;
Log.debug ("system init thread split");
Log.debug ("init param: "+init);
/*
* Unfortunately, the Servlet API provides no good mechanism to insert
* code that should be executed after all filter/servlet inits but
* before servicing any user request.
*
* Tomcat will not let any servlets service a request while still within
* the init() process so we use this thread split and small sleep
* timeout as an ugly hack.
*
* FIXME: find a better place for "system initialization" ?
*/
new Thread (new Runnable ()
{
public void run ()
{
Log.debug ("system init thread started");
final ServletContext context = getServletContext ();
try
{
Thread.sleep (1500);
Log.info ("system init begin");
Log.debug ("calling shell init for master environment");
final ShellSession.Instance shell = Shell.init (context);
final Transport config = Transport.handler (init, context);
Log.debug ("init config resource: "+config);
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance ();
Document xml = factory.newDocumentBuilder ().parse (config.get ());
if (!TAG_INIT.equals (xml.getDocumentElement ().getNodeName ()))
{
throw new IllegalArgumentException ("expecting <"+TAG_INIT+"> document, got: "+xml.getDocumentElement ());
}
NodeList tags = xml.getElementsByTagName (TAG_BOOT_WAIT);
for (int t=0; t<tags.getLength (); t++) try
{
//final String bootwait = tags.item (t).getNodeValue ();
final Node bootwait = tags.item (t).getFirstChild ();
Log.info ("bootwait: "+bootwait);
if (bootwait!=null)
{
//final Transport resource = Transport.handler (bootwait.getNodeValue (), context);
//Log.debug ("bootwait resource: "+resource);
- Shell.exec (bootwait.getNodeValue (), null, shell, context, null, null, null);
+ // FIXME (user)
+ Shell.exec (bootwait.getNodeValue (), null, new TaskData (shell, context, null));
//shell.saveTo ("local:/proc/shell/"+shell.getId (), context);
}
}
catch (final ServletException e)
{
Log.error ("failure executing bootwait", e);
}
catch (final IOException e)
{
Log.error ("failure executing bootwait", e);
}
}
catch (final InterruptedException e)
{
Log.warn ("interrupted", e);
}
catch (final IOException e)
{
Log.fatal ("failure loading init file: "+init, e);
}
catch (final ParserConfigurationException e)
{
Log.fatal ("failure loading init file: "+init, e);
}
catch (final SAXException e)
{
Log.fatal ("failure loading init file: "+init, e);
}
catch (final IllegalArgumentException e)
{
Log.fatal ("failure loading init file: "+init, e);
}
finally
{
Log.info ("system init end");
}
}
}).start();
}
}
diff --git a/src/com/iovar/web/usr/bin/XArgs.java b/src/com/iovar/web/usr/bin/XArgs.java
--- a/src/com/iovar/web/usr/bin/XArgs.java
+++ b/src/com/iovar/web/usr/bin/XArgs.java
@@ -1,187 +1,187 @@
/*
- * Copyright (C) 2014 Lannocc Technologies
+ * Copyright (C) 2014-2015 Lannocc Technologies
* @%@~LICENSE~@%@
*/
package com.iovar.web.usr.bin;
// local imports:
import com.iovar.web.*;
import com.iovar.web.bin.shell.*;
import com.iovar.web.bin.shell.task.*;
import com.iovar.web.dev.*;
import com.iovar.web.proc.*;
// java imports:
import java.io.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;
// 3rd-party imports:
/**
* @author shawn@lannocc.com
*/
public class XArgs extends HttpServlet
{
protected void doHead (final HttpServletRequest req, final HttpServletResponse resp) throws IOException
{
Utils.doHead (this, req, resp);
}
protected void doGet (final HttpServletRequest req, final HttpServletResponse resp) throws IOException
{
Utils.doGet (this, req, resp);
}
protected void doPut (final HttpServletRequest req, final HttpServletResponse resp) throws IOException
{
Utils.doPut (this, req, resp);
}
public void usage (final HttpServletResponse resp) throws IOException
{
Log.debug ("displaying usage");
resp.setContentType ("text/plain");
final PrintWriter out = resp.getWriter ();
out.println ("usage: xargs [options] <command> [args]");
out.println ();
out.println ("Build arguments from standard input and execute <command>.");
out.println ();
out.println ("Options:");
out.println (" ?help - display this help screen");
out.println (" ?_=<name> - the named option is converted to an anonymous argument");
}
/**
* Execute. Anonymous parameters expected:
* arg[0]: command to execute
*/
protected void doPost (final HttpServletRequest req, final HttpServletResponse resp) throws IOException, ServletException
{
final ServletContext context = getServletContext ();
final String query = req.getQueryString ();
final Map<String,List<String>> params = Utils.getParams (query);
if (params.containsKey ("help"))
{
usage (resp);
Shell.exit (req, context, 0);
return;
}
final List<String> vals = params.get (null);
if (vals==null || vals.size ()<1)
{
usage (resp);
Shell.exit (req, context, 1);
return;
}
final String resource = vals.remove (0);
//final String resource = vals.get (0);
final List<String> converts = params.get ("_");
final InputStream in = req.getInputStream ();
//Utils.pipe (in, resp.getOutputStream ());
final ShellSession.Instance session = Shell.getSession (req, context);
/*
if (session==null)
{
Log.error ("unable to retrieve session");
throw new IllegalArgumentException ("need a session to exec a call");
}
*/
final Map<String,List<String>> callParams;
if ("application/x-www-form-urlencoded".equalsIgnoreCase (req.getContentType ()))
{
Log.info ("x-www-form-urlencoded xargs processing");
//Log.debug ("in: "+ (int) in.read ());
callParams = Utils.getParams (Utils.toString (in));
}
else
{
Log.info ("default xargs processing");
callParams = new HashMap<String,List<String>> ();
final List<String> args = new ArrayList<String> ();
// FIXME: do this more effeciently, possibly utilize bin.shell.task package for parsing
StringBuffer arg = new StringBuffer ();
for (int c; (c = in.read ())>=0; )
{
if (' '==c || '\t'==c || '\r'==c || '\n'==c)
{
if (arg.length ()>0)
{
args.add (arg.toString ());
arg = new StringBuffer ();
}
}
else
{
arg.append ((char) c);
}
}
if (arg.length ()>0)
{
args.add (arg.toString ());
}
if (! args.isEmpty ())
{
callParams.put (null, args);
}
}
if (vals.size ()>0)
{
Log.debug ("passed-in arguments to add: "+vals);
List<String> anons = callParams.get (null);
if (anons==null)
{
anons = new ArrayList<String> ();
callParams.put (null, anons);
}
anons.addAll (0, vals);
}
if (converts!=null)
{
Log.debug ("parameters to convert: "+converts);
List<String> anons = callParams.get (null);
if (anons==null)
{
anons = new ArrayList<String> ();
callParams.put (null, anons);
}
for (final String convert : converts)
{
Log.debug ("converting: "+convert);
final List<String> cvals = callParams.remove (convert);
if (cvals!=null && cvals.size ()>0)
{
Log.debug ("adding anon args: "+cvals);
anons.addAll (cvals);
}
}
}
- final Return r = Shell.exec (resource, callParams, session, context, req.getRemoteUser (), null, null);
+ final Return r = Shell.exec (resource, callParams, new TaskData (session, context, req.getRemoteUser ()));
resp.setContentType (r.type);
Utils.pipe (r.data, resp.getOutputStream ());
Shell.exit (req, context, r.status.affirmative () ? 0 : 2);
}
}
diff --git a/src/com/iovar/web/usr/sbin/Cron.java b/src/com/iovar/web/usr/sbin/Cron.java
--- a/src/com/iovar/web/usr/sbin/Cron.java
+++ b/src/com/iovar/web/usr/sbin/Cron.java
@@ -1,324 +1,321 @@
/*
- * Copyright (C) 2014 Lannocc Technologies
+ * Copyright (C) 2014-2015 Lannocc Technologies
* @%@~LICENSE~@%@
*/
package com.iovar.web.usr.sbin;
// local imports:
import com.iovar.web.*;
import com.iovar.web.bin.shell.*;
import com.iovar.web.bin.shell.task.Call;
import com.iovar.web.bin.shell.task.Return;
+import com.iovar.web.bin.shell.task.TaskData;
import com.iovar.web.dev.*;
import com.iovar.web.proc.*;
// java imports:
import java.io.*;
import java.text.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;
// 3rd-party imports:
/**
* Execute commands on a schedule.
*
* @author shawn@lannocc.com
*/
public class Cron extends HttpServlet
{
protected void doHead (final HttpServletRequest req, final HttpServletResponse resp) throws IOException
{
Utils.doHead (this, req, resp);
}
protected void doGet (final HttpServletRequest req, final HttpServletResponse resp) throws IOException
{
Utils.doGet (this, req, resp);
}
protected void doPut (final HttpServletRequest req, final HttpServletResponse resp) throws IOException
{
Utils.doPut (this, req, resp);
}
public void usage (final HttpServletResponse resp) throws IOException
{
Log.debug ("displaying usage");
resp.setContentType ("text/plain");
final PrintWriter out = resp.getWriter ();
out.println ("usage: cron [<frequency> <command> [<arg>...]");
out.println ();
out.println ("Execute a given command on the given frequency.");
out.println ("The <frequency> may be 'daily', a given time, or number of seconds.");
out.println ();
out.println ("Will list currently scheduled jobs if called without arguments.");
out.println ();
out.println ("Options:");
out.println (" ?help - display this help screen");
out.println (" ?name= - give the job a unique label to prevent multiple instances");
}
/**
* Execute.
*/
protected void doPost (final HttpServletRequest req, final HttpServletResponse resp) throws IOException
{
final ServletContext context = getServletContext ();
final String query = req.getQueryString ();
final Map<String,List<String>> params = Utils.getParams (query);
if (params.containsKey ("help"))
{
usage (resp);
Shell.exit (req, context, 0);
return;
}
final ShellSession.Instance shell = Shell.getSession (req, context);
if (shell==null)
{
Log.error ("unable to retrieve session");
throw new IllegalArgumentException ("need a session for path information");
}
Map<String,Job> labeled = (Map<String,Job>) context.getAttribute (Job.class.getName ()+".labeled");
if (labeled==null)
{
labeled = new HashMap<String,Job> ();
context.setAttribute (Job.class.getName ()+".labeled", labeled);
}
List<Job> unlabeled = (List<Job>) context.getAttribute (Job.class.getName ()+".unlabeled");
if (unlabeled==null)
{
unlabeled = new ArrayList<Job> ();
context.setAttribute (Job.class.getName ()+".unlabeled", unlabeled);
}
final List<String> resources = params.get (null);
if (resources==null || resources.isEmpty ())
{
resp.setContentType ("text/plain");
resp.getWriter ().println ("cron: currently scheduled jobs:");
listJobs (labeled, unlabeled, resp.getWriter ());
Shell.exit (req, context, 0);
return;
}
else if (resources.size ()<2)
{
usage (resp);
Shell.exit (req, context, 1);
return;
}
final String user = req.getRemoteUser ();
final String freq = resources.get (0);
final String cmd = resources.get (1);
final List<String> args = new ArrayList<String> ();
final List<String> name = params.get ("name");
if (resources.size ()>2)
{
for (int i=2; i < resources.size (); i++)
{
args.add (resources.get (i));
}
}
final DateFormat tf = new SimpleDateFormat ("HH:mm:ss");
tf.setLenient (true);
try
{
if (name==null || name.isEmpty () || !labeled.containsKey (name.get (0)))
{
Job job;
if ("daily".equals (freq))
{
- job = new DailyJob (cmd, args, shell, context, user, null);
+ job = new DailyJob (cmd, args, new TaskData (shell, context, user), null);
}
else try
{
- job = new HertzJob (Integer.parseInt (freq), cmd, args, shell, context, user);
+ job = new HertzJob (Integer.parseInt (freq), cmd, args, new TaskData (shell, context, user));
}
catch (final NumberFormatException e)
{
try
{
final Date time = tf.parse (freq);
final Calendar cal = Calendar.getInstance ();
cal.setTime (time);
- job = new DailyJob (cmd, args, shell, context, user, cal);
+ job = new DailyJob (cmd, args, new TaskData (shell, context, user), cal);
}
catch (final ParseException ee)
{
throw new IllegalArgumentException ();
}
}
job.thread = new Thread (job);
job.thread.start ();
Log.info ("job thread started: "+job);
if (name!=null && ! name.isEmpty ())
{
labeled.put (name.get (0), job);
}
else
{
unlabeled.add (job);
}
}
}
catch (final IllegalArgumentException e)
{
Log.warn (e);
resp.getWriter ().println ("cron: not 'daily', a time (hh:mm:ss) or a number: "+freq);
Shell.exit (req, context, 2);
return;
}
Shell.exit (req, context, 0);
}
static void listJobs (final Map<String,Job> labeled, final List<Job> unlabeled, final PrintWriter out)
{
for (final Map.Entry<String,Job> entry : labeled.entrySet ())
{
final String label = entry.getKey ();
out.print ("<"+label+"> ");
out.println (entry.getValue ());
}
for (final Job job : unlabeled)
{
out.println (job);
}
}
static abstract class Job implements Runnable
{
Thread thread;
final Call call;
- final ShellSession.Instance shell;
- final ServletContext context;
- final String user;
+ final TaskData task;
- Job (final String cmd, final List<String> args, final ShellSession.Instance shell, final ServletContext context, final String user)
+ Job (final String cmd, final List<String> args, final TaskData task)
{
Map argMap = new HashMap<String,List<String>> ();
if (args!=null && !args.isEmpty ()) argMap.put (null, args);
this.call = Call.valueOf (cmd, argMap);
- this.shell = shell;
- this.context = context;
- this.user = user;
+ this.task = task;
}
public void run ()
{
Log.debug ("cron job running: "+this);
while (true) try
{
idle ();
- call.exec (shell, context, user, null, null);
+ call.exec (task);
}
catch (final IOException e)
{
Log.error (e);
}
catch (final ServletException e)
{
Log.error (e);
}
catch (final InterruptedException e)
{
Log.warn ("interrupted: "+e);
break;
}
catch (final Exception e)
{
Log.fatal ("uncaught exception", e);
break;
}
}
abstract void idle () throws InterruptedException;
}
static class HertzJob extends Job
{
final int secs;
- HertzJob (final int secs, final String cmd, final List<String> args, final ShellSession.Instance shell, final ServletContext context, final String user)
+ HertzJob (final int secs, final String cmd, final List<String> args, final TaskData task)
{
- super (cmd, args, shell, context, user);
+ super (cmd, args, task);
this.secs = secs;
}
void idle () throws InterruptedException
{
Thread.sleep (secs*1000);
}
public String toString ()
{
return "[every "+secs+" secs] "+call;
}
}
static class DailyJob extends Job
{
final Calendar next;
- DailyJob (final String cmd, final List<String> args, final ShellSession.Instance shell, final ServletContext context, final String user, final Calendar time)
+ DailyJob (final String cmd, final List<String> args, final TaskData task, final Calendar time)
{
- super (cmd, args, shell, context, user);
+ super (cmd, args, task);
next = Calendar.getInstance ();
if (time!=null)
{
next.set (Calendar.SECOND, time.get (Calendar.SECOND));
next.set (Calendar.MINUTE, time.get (Calendar.MINUTE));
next.set (Calendar.HOUR_OF_DAY, time.get (Calendar.HOUR_OF_DAY));
}
else
{
next.clear (Calendar.SECOND);
next.clear (Calendar.MINUTE);
next.clear (Calendar.HOUR);
}
if (next.compareTo (Calendar.getInstance ())<0)
{
next.roll (Calendar.DATE, true);
}
}
void idle () throws InterruptedException
{
while (next.getTime ().compareTo (new Date ()) >= 0)
{
Thread.sleep (10000); // 10 seconds
}
next.roll (Calendar.DATE, true);
}
public String toString ()
{
return "[every day (next: "+next.getTime ()+")] "+call;
}
}
}

File Metadata

Mime Type
text/x-diff
Expires
Sun, Sep 13, 4:27 PM (2 w, 3 d ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
3534
Default Alt Text
(120 KB)

Event Timeline