Executor.cc
Go to the documentation of this file.
1 //
2 // Copyright (c) 2020 Fraunhofer Institute for Applied Information Technology (FIT)
3 // Network Research Group (NET)
4 // Schloss Birlinghoven, 53754 Sankt Augustin, GERMANY
5 // Contact: support@wiback.org
6 //
7 // This file is part of the SENF code tree.
8 // It is licensed under the 3-clause BSD License (aka New BSD License).
9 // See LICENSE.txt in the top level directory for details or visit
10 // https://opensource.org/licenses/BSD-3-Clause
11 //
12 
13 
17 #include "Executor.hh"
18 //#include "Executor.ih"
19 
20 // Custom includes
21 #include <boost/range/iterator_range.hpp>
22 #include <boost/bind.hpp>
23 #include <boost/format.hpp>
24 #include <boost/preprocessor/stringize.hpp>
25 #include <boost/algorithm/string/join.hpp>
26 #include <senf/Utils/senfassert.hh>
27 #include <senf/Utils/Range.hh>
29 #include "Server.hh"
30 
31 //#include "Executor.mpp"
32 #define prefix_
33 //-/////////////////////////////////////////////////////////////////////////////////////////////////
34 
35 namespace {
36 
37  struct TraversTokens {
38  typedef std::string const & result_type;
39  result_type operator()(senf::console::Token const & token) const {
40  return token.value();
41  }
42  };
43 
44 }
45 
46 //-/////////////////////////////////////////////////////////////////////////////////////////////////
47 // senf::console::Executor
48 
50  const
51 {
52  SENF_ASSERT( ! cwd_.empty(), "Internal error: CWD history empty ?" );
53  while (cwd_.size()>1 && (cwd_.back().expired() || ! cwd_.back().lock()->active()))
54  cwd_.pop_back();
55  return * cwd_.back().lock();
56 }
57 
59  const
60 {
61  if (skipping())
62  return "";
63  senf::IGNORE( cwd() ); // ensure, cwd is live.
64  return "/" + boost::algorithm::join(
65  senf::make_transform_range(
66  boost::make_iterator_range(boost::next(cwd_.begin()), cwd_.end()),
67  boost::bind(&DirectoryNode::name, boost::bind(&DirectoryNode::weak_ptr::lock, _1))),
68  "/" );
69 }
70 
71 prefix_ void senf::console::Executor::execute(std::ostream & output,
72  ParseCommandInfo const & command)
73 {
74  SENF_LOG(( "Executing: " << command ));
75 
76  if (! skipping())
77  senf::IGNORE( cwd() ); // Prune the cwd path of expired entries
78 
79  try {
80  switch(command.builtin()) {
81  case ParseCommandInfo::NoBuiltin :
82  if (skipping())
83  return;
84  exec(output, command);
85  break;
86 
87  case ParseCommandInfo::BuiltinCD :
88  if (skipping())
89  break;
90  try {
91  // The parser ensures, we have exactly one argument
92  cd(command.commandPath());
93  }
94  catch (IgnoreCommandException &) {
96  "'cd' cannot be skipped (don't use 'cd' in conf-files)");
97  }
98  break;
99 
100  case ParseCommandInfo::BuiltinLS :
101  if (skipping())
102  break;
103  // The parser ensures, we have either one or no argument
104  ls( output, command.commandPath() );
105  break;
106 
107  case ParseCommandInfo::BuiltinLL :
108  if (skipping())
109  break;
110  // The parser ensures, we have either one or no argument
111  ll( output, command.commandPath() );
112  break;
113 
114  case ParseCommandInfo::BuiltinLR :
115  if (skipping())
116  break;
117  // The parser ensures, we have either one or no argument
118  lr( output, command.commandPath() );
119  break;
120 
121  case ParseCommandInfo::BuiltinPUSHD :
122  // The parser ensures, we have exactly one argument
123  if (skipping())
124  pushd(command.commandPath());
125  else
126  exec(output, command);
127  break;
128 
129  case ParseCommandInfo::BuiltinPOPD :
130  // The parser ensures, we have no arguments
131  popd();
132  break;
133 
134  case ParseCommandInfo::BuiltinEXIT :
135  if (skipping())
136  break;
137  // The parser ensures, we have no arguments
138  exit();
139  break;
140 
141  case ParseCommandInfo::BuiltinHELP :
142  if (skipping())
143  break;
144  // The parser ensures, we have either one or no arguments
145  help( output, command.commandPath() );
146  break;
147 
148  case ParseCommandInfo::BuiltinECHO :
149  if (skipping())
150  break;
151  echo( output, command.tokens() );
152  break;
153 
154  }
155  }
156  catch (InvalidPathException & ex) {
157  throw SyntaxErrorException("invalid path") << " '" << ex.path << "'";
158  }
159  catch (InvalidDirectoryException & ex) {
160  throw SyntaxErrorException("invalid directory") << " '" << ex.path << "'";
161  }
162  catch (InvalidCommandException &) {
163  throw SyntaxErrorException("invalid command");
164  }
165  catch (IgnoreCommandException &) {}
166 }
167 
170 {
171  try {
172  return traverseNode(command.commandPath());
173  }
174  catch (InvalidPathException & ex) {
175  throw SyntaxErrorException("invalid path") << " '" << ex.path << "'";
176  }
177  catch (InvalidDirectoryException & ex) {
178  throw SyntaxErrorException("invalid directory") << " '" << ex.path << "'";
179  }
180 }
181 
182 prefix_ void senf::console::Executor::exec(std::ostream & output,
183  ParseCommandInfo const & command)
184 {
185  try {
186  GenericNode & node ( traverseNode(command.commandPath()) );
187  DirectoryNode * dir ( dynamic_cast<DirectoryNode*>(&node) );
188  if ( dir ) {
189  if (! command.tokens().empty())
190  throw InvalidCommandException();
191  if (command.builtin() == ParseCommandInfo::BuiltinPUSHD)
192  pushd( command.commandPath() );
193  else if (autocd_) {
194  cd(command.commandPath());
195  }
196  else
197  throw InvalidCommandException();
198  } else {
199  boost::any rv;
200  dynamic_cast<CommandNode &>(node)(rv, output, command);
201  if (command.builtin() == ParseCommandInfo::BuiltinPUSHD) {
202  DirectoryNode::ptr rvdir;
203  try {
204  rvdir = boost::any_cast<DirectoryNode::ptr>(rv);
205  }
206  catch (boost::bad_any_cast &) {
207  throw InvalidCommandException();
208  }
209  Path newDir (cwd_);
210  newDir.push_back(rvdir);
211  dirstack_.push_back(Path());
212  dirstack_.back().swap(cwd_);
213  cwd_.swap(newDir);
214  }
215  }
216  }
217  catch (IgnoreCommandException &) {
218  if (command.builtin() == ParseCommandInfo::BuiltinPUSHD) {
219  dirstack_.push_back(Path());
220  dirstack_.back().swap(cwd_);
221  }
222  else
223  throw;
224  }
225 }
226 
227 
228 prefix_ void senf::console::Executor::cd(ParseCommandInfo::TokensRange dir)
229 {
230  if (dir.size() == 1 && *dir.begin() == WordToken("-")) {
231  cwd_.swap(oldCwd_);
232  senf::IGNORE( cwd() ); // Prune any expired items
233  }
234  else {
235  // We need to use a temporary so an error somewhere while traversing the dir does not cause
236  // the current directory to change.
237  Path newDir (cwd_);
238  traverseDirectory(dir, newDir);
239  oldCwd_.swap(cwd_);
240  cwd_.swap(newDir);
241  }
242 }
243 
244 prefix_ void senf::console::Executor::ls(std::ostream & output,
246 {
247  Path dir (cwd_);
248  traverseDirectory(path, dir);
249  DirectoryNode & node (*dir.back().lock());
250  DirectoryNode::child_iterator i (node.children().begin());
251  DirectoryNode::child_iterator const i_end (node.children().end());
252  for (; i != i_end; ++i)
253  output << i->first << "\n";
254 }
255 
256 prefix_ void senf::console::Executor::echo(std::ostream & output,
258 {
259  ParseCommandInfo::TokensRange::iterator i (args.begin());
260  ParseCommandInfo::TokensRange::iterator i_end (args.end());
261  while (i != i_end) {
262  output << (i++)->value();
263  if (i != i_end) output << ' ';
264  }
265  output << '\n';
266 }
267 
268 prefix_ void senf::console::Executor::ll(std::ostream & output,
270 {
271 # define HELP_COLUMN 28
272 
273  unsigned width (Client::getWidth(output, 80u, 60u)-(HELP_COLUMN+1));
274  Path dir (cwd_);
275  traverseDirectory(path, dir);
276  DirectoryNode & node (*dir.back().lock());
277  DirectoryNode::child_iterator i (node.children().begin());
278  DirectoryNode::child_iterator const i_end (node.children().end());
279  boost::format fmt ("%s%s %|" BOOST_PP_STRINGIZE(HELP_COLUMN) "t|%s\n");
280  for (; i != i_end; ++i)
281  output << fmt
282  % i->first
283  % ( i->second->isDirectory()
284  ? "/"
285  : i->second->isLink()
286  ? "@"
287  : "" )
288  % i->second->shorthelp().substr(0,width);
289 
290 # undef HELP_COLUMN
291 }
292 
293 # define HELP_COLUMN 40
294 
295 namespace {
296 
297  typedef std::map<senf::console::DirectoryNode*,std::string> NodesMap;
298 
299  void dolr(std::ostream & output, unsigned width, NodesMap & nodes, std::string const & base,
300  unsigned level, senf::console::DirectoryNode & node)
301  {
302  boost::format fmt ("%s%s%s %|" BOOST_PP_STRINGIZE(HELP_COLUMN) "t|%s\n");
303  std::string pad (2*level, ' ');
305  senf::console::DirectoryNode::child_iterator const i_end (node.children().end());
306  for (; i != i_end; ++i) {
307  if (i->second->followLink().isDirectory()) {
308  senf::console::DirectoryNode & subnode (
309  static_cast<senf::console::DirectoryNode&>(i->second->followLink()));
310  NodesMap::iterator j (nodes.find(&subnode));
311  if (j == nodes.end()) {
312  output << fmt
313  % pad % i->first
314  % ( i->second->isDirectory() ? "/" : i->second->isLink() ? "@" : "" )
315  % i->second->shorthelp().substr(0,width);
316  std::string subbase (base);
317  if (! subbase.empty())
318  subbase += "/";
319  subbase += i->first;
320  nodes.insert(std::make_pair(&subnode, subbase));
321  dolr(output, width, nodes, subbase, level+1, subnode);
322  } else
323  output << pad << i->first
324  << ( i->second->isDirectory() ? "/" : i->second->isLink() ? "@" : "" )
325  << " -> " << j->second << "\n";
326  } else {
327  output << fmt
328  % pad % i->first
329  % ( i->second->isDirectory() ? "/" : i->second->isLink() ? "@" : "" )
330  % i->second->shorthelp().substr(0,width);
331  }
332  }
333  }
334 
335 }
336 
337 prefix_ void senf::console::Executor::lr(std::ostream & output,
338  ParseCommandInfo::TokensRange path)
339 {
340  Path dir (cwd_);
341  traverseDirectory(path, dir);
342  DirectoryNode & node (*dir.back().lock());
343  NodesMap nodes;
344  dolr(output, Client::getWidth(output, 80u, 60u)-(HELP_COLUMN+1),
345  nodes, "", 0, node);
346 }
347 
348 #undef HELP_COLUMN
349 
350 prefix_ void senf::console::Executor::pushd(ParseCommandInfo::TokensRange dir)
351 {
352  Path newDir (cwd_);
353  if (! skipping()) {
354  try {
355  traverseDirectory(dir, newDir);
356  }
357  catch (IgnoreCommandException &) {
358  newDir.clear();
359  }
360  }
361  dirstack_.push_back(Path());
362  dirstack_.back().swap(cwd_);
363  cwd_.swap(newDir);
364 }
365 
366 prefix_ void senf::console::Executor::popd()
367 {
368  if (! dirstack_.empty()) {
369  cwd_.swap(dirstack_.back());
370  dirstack_.pop_back();
371  }
372 }
373 
374 prefix_ void senf::console::Executor::exit()
375 {
376  throw ExitException();
377 }
378 
379 prefix_ void senf::console::Executor::help(std::ostream & output,
380  ParseCommandInfo::TokensRange path)
381 {
382  GenericNode const & node (traverseNode(path));
383  // output << prettyName(typeid(node)) << " at " << node.path() << "\n\n";
384  node.help(output);
385  output << std::flush;
386 }
387 
389 senf::console::Executor::traverseNode(ParseCommandInfo::TokensRange const & path)
390 {
391  if (path.empty())
392  return *cwd_.back().lock();
393  try {
394  Path dir (cwd_);
395  traverseDirectory(boost::make_iterator_range(
396  path.begin(),
397  boost::prior(path.end())),
398  dir);
399  // For auto-cd support we need to check against '.' and '..' here too
400  Token const & tok (*boost::prior(path.end()));
401  if (tok == WordToken("..")) {
402  if (dir.size() > 1)
403  dir.pop_back();
404  return *dir.back().lock();
405  }
406  DirectoryNode & base (*dir.back().lock());
407  if (tok == WordToken(".") || tok == NoneToken())
408  return base;
409  std::string const & name (complete(base, tok.value()));
410  if (policy_)
411  policy_( base, name );
412  return dir.back().lock()->get(name);
413  }
414  catch (UnknownNodeNameException &) {
415  throw InvalidPathException( boost::algorithm::join(
416  senf::make_transform_range(path, boost::bind(&Token::value, _1)),
417  "/"));
418  }
419 }
420 
421 prefix_ void
422 senf::console::Executor::traverseDirectory(ParseCommandInfo::TokensRange const & path,
423  Path & dir)
424 {
425  std::string errorPath;
426  try {
427  ParseCommandInfo::TokensRange::const_iterator i (path.begin());
428  ParseCommandInfo::TokensRange::const_iterator const i_end (path.end());
429  for (; i != i_end; ++i) {
430  if (i != path.begin())
431  errorPath += "/";
432  errorPath += i->value();
433  if (*i == NoneToken()) {
434  if (i == path.begin()) {
435  dir.clear();
436  dir.push_back(root_);
437  }
438  }
439  else if (*i == WordToken("..")) {
440  if (dir.size() > 1)
441  dir.pop_back();
442  }
443  else if (*i == WordToken("."))
444  ;
445  else {
446  DirectoryNode & base (*dir.back().lock());
447  std::string name (complete(base, i->value()));
448  if (policy_)
449  policy_( base, name );
450  dir.push_back(base[name].thisptr());
451  }
452  }
453  }
454  catch (std::bad_cast &) {
455  throw InvalidDirectoryException(errorPath);
456  }
457  catch (UnknownNodeNameException &) {
458  throw InvalidDirectoryException(errorPath);
459  }
460 }
461 
462 prefix_ std::string senf::console::Executor::complete(DirectoryNode & dir,
463  std::string const & name)
464 {
465  if (! dir.hasChild(name)) {
466  DirectoryNode::ChildrenRange completions (dir.completions(name));
467  if (has_one_elt(completions))
468  return completions.begin()->first;
469  }
470  return name;
471 }
472 
474  std::ostream & os)
475 {
476  if (value)
477  os << "<Directory at '" << value->path() << "'>";
478  else
479  os << "<Null Directory>";
480 }
481 
482 //-/////////////////////////////////////////////////////////////////////////////////////////////////
483 #undef prefix_
484 //#include "Executor.mpp"
485 
486 
487 // Local Variables:
488 // mode: c++
489 // fill-column: 100
490 // comment-column: 40
491 // c-file-style: "senf"
492 // indent-tabs-mode: nil
493 // ispell-local-dictionary: "american"
494 // compile-command: "scons -u test"
495 // End:
TokensRange tokens() const
All argument tokens.
boost::shared_ptr< DirectoryNode > ptr
Definition: Node.hh:414
Config/console tree directory node.
Definition: Node.hh:406
void senf_console_format_value(DirectoryNode::ptr value, std::ostream &os)
Definition: Executor.cc:473
void execute(std::ostream &output, ParseCommandInfo const &command)
Execute command.
Definition: Executor.cc:71
Server public header.
Syntax error parsing command arguments exception.
Definition: Parse.hh:472
void complete(LineEditor &editor, Completer completer)
Thrown by the SecurityPolicy to silently ignore a command.
Definition: Executor.hh:64
Single parsed console command.
Definition: Parse.hh:363
TokensRange commandPath() const
Command path.
bool has_one_elt(Range r)
GenericNode & getNode(ParseCommandInfo const &command)
Definition: Executor.cc:169
DirectoryNode & cwd() const
Current working directory.
Definition: Executor.cc:49
ChildMap::const_iterator child_iterator
Definition: Node.hh:419
BuiltinCommand builtin() const
Command type.
std::string const & value() const
String value of token.
#define SENF_ASSERT(x, comment)
boost::iterator_range< token_iterator > TokensRange
Definition: Parse.hh:378
Config/console tree command node.
Definition: Node.hh:563
#define prefix_
Definition: Executor.cc:32
ChildrenRange children() const
Return iterator range over all children.
unspecified_keyword_type name
Argument name.
Config/console node tree base-class.
Definition: Node.hh:250
void format(Type const &value, std::ostream &os)
Format return value.
Executor public header.
#define SENF_LOG(args)
std::string cwdPath() const
Return pathname of current directory.
Definition: Executor.cc:58
Single argument token.
Definition: Parse.hh:234
#define HELP_COLUMN
Definition: Executor.cc:293